/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice,
     this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright 
     notice, this list of conditions and the following disclaimer in 
     the documentation and/or other materials provided with the distribution.

  3. The names of the authors may not be used to endorse or promote products
     derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.jcraft.jsch;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Vector;

/**
 * 2025.7.17: 主类
 * <p>
 * 2025.6.27: session是jsch工程中的Java的数据结构，跟ssh协议无关。
 * 2025.6.27: 它代表了一次会话;
 * <p>
 * 2025.6.30: 按照SSH协议架构，标准将SSH分成三层：传输层协议、用户认证协议、连接协议
 * 2025.6.30: 1) 最底层的是传输层协议，它负责认证服务器，加密数据，确保数据完整性，虽然它运行在TCP之上，
 * 但其实它可以运行在任意可靠的数据流之上；
 * 2025.6.30: 2) 第二层是用户认证协议，它负责认证使用者是否是ssh服务器的用户，
 * Public Key Authentication登陆ssh就将在这一层实现；
 * 2025.6.30: 3) 最上层是连接协议，它将把多路加密的通道转换成逻辑上的Channel
 */
@Slf4j
public class Session implements Runnable {

    // http://ietf.org/internet-drafts/draft-ietf-secsh-assignednumbers-01.txt
    static final int SSH_MSG_DISCONNECT = 1;
    static final int SSH_MSG_IGNORE = 2;
    static final int SSH_MSG_UNIMPLEMENTED = 3;
    static final int SSH_MSG_DEBUG = 4;
    static final int SSH_MSG_SERVICE_REQUEST = 5;
    static final int SSH_MSG_SERVICE_ACCEPT = 6;
    /**
     * 2025.7.10: 发送Key Exchange Init包
     * 用于认证服务器的公钥属于什么公钥算法：
     * 1. 客户端->服务端的数据加密使用什么算法，
     * 2. 服务端->客户端的数据加密使用什么算法，
     * 3. 客户端->服务端的数据使用什么MAC算法，
     * 4. 服务端->客户端的数据使用什么MAC算法，
     * 5. 客户端->服务端的数据使用什么压缩算法，
     * 6. 服务端->客户端的数据使用什么压缩算法
     * 7. 同时还要提供和估测，客户端到服务端的数据使用何种语言，
     * 8. 服务端到客户端的数据使用何种语言
     */
    static final int SSH_MSG_KEXINIT = 20;
    /**
     * 2025.7.10: 扩展协商
     */
    static final int SSH_MSG_NEWKEYS = 21;
    /**
     * 2025.7.10: DH(Diffie-Hellman Key Exchange)算法是历史上的第一个密钥交换算法，只有双方都参与才能生成shared secret。
     */
    static final int SSH_MSG_KEXDH_INIT = 30;
    static final int SSH_MSG_KEXDH_REPLY = 31;
    static final int SSH_MSG_KEX_DH_GEX_GROUP = 31;
    static final int SSH_MSG_KEX_DH_GEX_INIT = 32;
    static final int SSH_MSG_KEX_DH_GEX_REPLY = 33;
    static final int SSH_MSG_KEX_DH_GEX_REQUEST = 34;
    static final int SSH_MSG_GLOBAL_REQUEST = 80;
    static final int SSH_MSG_REQUEST_SUCCESS = 81;
    static final int SSH_MSG_REQUEST_FAILURE = 82;
    //2025.6.27: 开通通道
    static final int SSH_MSG_CHANNEL_OPEN = 90;
    //2025.7.11: 开通通道确认
    static final int SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91;
    //2025.7.11: 开通通道失败
    static final int SSH_MSG_CHANNEL_OPEN_FAILURE = 92;
    /**
     * 2025.7.2: 上文描述了窗口大小可以用来限制另一方发送的数据量，
     * 2025.7.2: 同时协议规定双方都可以通过下面的消息对窗口作出调整。
     * 2025.7.2: 接收方接收到这个消息以后，接收方可以根据这个给定的数增加窗口的大小，不论如何窗口最大为2^32-1字节。
     */
    static final int SSH_MSG_CHANNEL_WINDOW_ADJUST = 93;
    /**
     * 2025.7.2: 具体的数据，则通过下面的消息发送。
     * 2025.7.2: 单次可以发送数据的最大量取决于对方当前窗口尺寸和对方允许接受的最大packet值的最小值。
     * 2025.7.2: 对方每接受一个消息，窗口都会相应减少。
     * 2025.7.2: 规范期望实现可以对传输层packet size做出限制（任何关于接受数据的限制必须大于等于32768字节）
     */
    static final int SSH_MSG_CHANNEL_DATA = 94;
    /**
     * 2025.7.2: 协议提供了一些传递额外数据（比如stderr数据）的方法。
     */
    static final int SSH_MSG_CHANNEL_EXTENDED_DATA = 95;
    /**
     * 2025.7.2: 当任意一方不在往Channel发送更多数据的时候，它应该发送一个SSH_MSG_CHANNEL_EOF消息
     * 2025.7.2: 这个消息不会有明确的响应，但是它依旧应该被发送给对方不论对方是谁。
     * 2025.7.2: 需要注意的是，发送完这个消息后，Channel依旧是打开着的（只不过自己这一边不再发数据了），
     * 2025.7.2: 从另一个方向上还是可能过来更多的数据。这个消息并不会消耗窗口大小，即使窗口已经不可以。
     */
    static final int SSH_MSG_CHANNEL_EOF = 96;
    //2025.6.27: 关闭通道
    /**
     * 2025.7.2: 当任意一方希望结束Channel时，则应该发送SSH_MSG_CHANNEL_CLOSE。
     * 2025.7.2: 另一方必须也发送SSH_MSG_CHANNEL_CLOSE，除非它已经发送过SSH_MSG_CHANNEL_CLOSE（网络延迟）了。
     * 2025.7.2: 当一方既发送了又接收到SSH_MSG_CHANNEL_CLOSE消息，Channel就被关闭了，相关的资源可以被清理，
     * 2025.7.2: 本地的Channel number可以在下次打开Channel的时候重用。
     * 2025.7.2: 任意一方都可以直接发送SSH_MSG_CHANNEL_CLOSE而不需要与现发送SSH_MSG_CHANNEL_EOF
     */
    static final int SSH_MSG_CHANNEL_CLOSE = 97;
    //2025.6.27: 通道请求
    /**
     * 2025.7.2: 许多的channel type包含关于该channel type的更详细的扩充设定。
     * 2025.7.2: 比如说，为一个交互session请求一个虚拟终端。所有的明确Channel信息请求都是如下格式。
     * 2025.7.2: 如果want reply被设置成FALSE，不会有响应被回复给请求端。
     * 2025.7.2: 否则，响应可能包括SSH_MSG_CHANNEL_SUCCESS、SSH_MSG_CHANNEL_FAILURE或者要求继续提供信息的消息。
     * 2025.7.2: 如果接收端不认识或不支持这个扩充的明细，则返回SSH_MSG_CHANNEL_FAILURE
     * 2025.7.2: 通过如下消息可以让服务器为Session分配一个虚拟终端，character/row的优先级相比于pixels更高，除非他们被设置成0
     * 2025.7.2: 在shell或command被开始时之后，或许有环境变量需要被传递过去。
     * 2025.7.2: 然而在特权程序里不受控制的设置环境变量是一个很有风险的事情，
     * 2025.7.2: 所以规范推荐实现维护一个允许被设置的环境变量列表或者只有当sshd丢弃权限后设置环境变量。
     * 2025.7.2: 一旦一个Session被设置完毕，在远端就会有一个程序被启动。这个程序可以是一个Shell，
     * 2025.7.2: 也可以时一个应用程序或者是一个有着独立域名的子系统。
     * 2025.7.2: 下面的请求每个Channel（Session）只允许设置一个。
     */
    static final int SSH_MSG_CHANNEL_REQUEST = 98;
    static final int SSH_MSG_CHANNEL_SUCCESS = 99;
    static final int SSH_MSG_CHANNEL_FAILURE = 100;

    private static final int PACKET_MAX_SIZE = 256 * 1024;
    //2025.6.27: 服务端版本
    private byte[] serverVersion;                                 // server version
    //2025.6.27: 客户端版本
    private byte[] clientVersion = Util.str2byte("SSH-2.0-JSCH-" + JSch.VERSION); // client version
    //2025.7.15: 客户端支持的加密算法
    private byte[] byteClientSupportAlgorithm; // the payload of the client's SSH_MSG_KEXINIT
    //2025.7.15: 服务器端支持的加密算法
    private byte[] byteServerSupportAlgorithm; // the payload of the server's SSH_MSG_KEXINIT
    private byte[] K_S; // the host key
    //2025.7.11: 会话ID
    private byte[] session_id;

    private byte[] IVc2s;
    private byte[] IVs2c;
    private byte[] Ec2s;
    private byte[] Es2c;
    private byte[] MACc2s;
    private byte[] MACs2c;
    /**
     * 输入报文顺序号
     */
    private int seqInput = 0;
    /**
     * 输出报文顺序号
     */
    private int seqOutput = 0;

    String[] guess = null;
    private Cipher s2ccipher;
    private Cipher c2scipher;
    private MAC s2cmac;
    private MAC c2smac;
    //private byte[] mac_buf;
    private byte[] s2cmacResult1;
    private byte[] s2cmacResult2;
    /**
     * 2025.7.16: 压缩工具类(用于压缩发往服务器端的报文)
     */
    private Compression deflater;
    /**
     * 2025.7.16: 解压工具类(用于读取服务器端发送过来的报文)
     */
    private Compression inflater;

    private IO io;
    private Socket socket;
    private int timeout = 0;

    private volatile boolean isConnected = false;

    private boolean isAuthed = false;

    private Thread connectThread = null;
    private Object lock = new Object();

    boolean x11Forwarding = false;
    boolean agentForwarding = false;

    InputStream in = null;
    OutputStream out = null;

    /**
     * 随机数生成器，出于协议包整除的需要，补全ssh协议包
     */
    static Random random;

    Buffer buf;
    Packet packet;

    /**
     * 2025.7.11: socket工厂
     */
    SocketFactory socket_factory = null;

    static final int buffer_margin = 32 + // maximum padding length
            64 + // maximum mac length
            32;  // margin for deflater; deflater may inflate data

    private java.util.Hashtable config = null;

    private Proxy proxy = null;
    private UserInfo userinfo;

    private String hostKeyAlias = null;
    private int serverAliveInterval = 0;
    private int serverAliveCountMax = 1;

    private IdentityRepository identityRepository = null;
    private HostKeyRepository hostkeyRepository = null;

    protected boolean daemon_thread = false;

    private long kex_start_time = 0L;

    int max_auth_tries = 6;
    int auth_failures = 0;

    String host = "127.0.0.1";
    String org_host = "127.0.0.1";
    int port = 22;

    String username = null;
    byte[] password = null;

    JSch jsch;

    /**
     * 2025.6.27: 获取session
     *
     * @param jsch
     * @param username
     * @param host
     * @param port
     * @throws JSchException
     */
    Session(JSch jsch, String username, String host, int port) throws JSchException {
        super();
        this.jsch = jsch;
        //2025.6.27: 初始化缓存
        buf = new Buffer();
        //2025.6.27: 初始化ssh网络协议报文
        packet = new Packet(buf);
        this.username = username;
        this.org_host = this.host = host;
        this.port = port;

        applyConfig();

        if (this.username == null) {
            try {
                this.username = (String) (System.getProperties().get("user.name"));
            } catch (SecurityException e) {
                // ignore e
            }
        }

        if (this.username == null) {
            throw new JSchException("username is not given.");
        }
    }

    /**
     * 2025.6.27: 连接
     * 2025.6.30: 与服务器端建立连接（前面属于tcp协议层，后面输入ssh协议）
     * <p>
     * 2025.7.11: 入口函数
     *
     * @throws JSchException
     */
    public void connect() throws JSchException {
        connect(timeout);
    }

    /**
     * 连接
     * 2025.6.30: 与服务器端建立连接（前面属于tcp协议层，后面输入ssh协议）
     * <p>
     * 2025.6.30: 这里只处理ssh协议第一层和第二层的信息
     * 2025.7.10：重要入口。后面会操作ssh协议
     * 2025.7.15：1.连接建立 2.版本协商; 3.算法协商; 4.密钥交换; 5.用户认证
     *
     * @param connectTimeout 超时时间
     * @throws JSchException
     */
    public void connect(int connectTimeout) throws JSchException {
        log.info("[ssh协议的第一层] 开始建立ssh协议的传输层连接");

        //2025.6.27: 判断是否已经连接
        if (isConnected) {
            throw new JSchException("session is already connected");
        }

        io = new IO();
        if (random == null) {
            try {
                Class c = Class.forName(getConfig("random"));
                random = (Random) (c.newInstance());
            } catch (Exception e) {
                throw new JSchException(e.toString(), e);
            }
        }
        //设置随机数产生器
        //2025.7.2: 用于生成sessionId用
        Packet.setRandom(random);

        log.info("[ssh协议的第一层] 准备与服务器 {} 的端口 {}建立连接", host, port);

        try {
            int i, j;
            //2025.6.27: 如果没有代理
            if (proxy == null) {
                InputStream in;
                OutputStream out;
                //2025.6.27: 如果不存在套接字工厂，则通过工具类直接创建套接字
                if (socket_factory == null) {
                    //2025.6.27：【重要】创建套接字
                    socket = Util.createSocket(host, port, connectTimeout);
                    log.info("[ssh协议的第一层] 直接创建套接字socket成功（没有用工厂）, 建立连接完毕");
                    in = socket.getInputStream();
                    out = socket.getOutputStream();
                } else {
                    //2025.6.27: 如果存在，则用套接字工厂创建。
                    //2025.6.27: 也就是说通过连接池来管理套接字。因为创建套接字是一种昂贵的行为
                    //2025.7.10: 建立连接是很重要的一步，标志着TCP层的交互已经完成。后面即将进入应用层协议（ssh）的交互
                    socket = socket_factory.createSocket(host, port);
                    in = socket_factory.getInputStream(socket);
                    out = socket_factory.getOutputStream(socket);
                    log.info("[ssh协议的第一层] 通过工厂创建套接字socket成功, 建立连接完毕");
                }
                //if(timeout>0){ socket.setSoTimeout(timeout); }
                //2025.6.27: 设置tcp是否延迟标志位
                socket.setTcpNoDelay(true);
                //2025.6.28: 将socket的InputStream放到IO类中
                io.setInputStream(in);
                //2025.6.28: 将socket的OutputStream放到IO类中
                io.setOutputStream(out);
            } else {
                synchronized (proxy) {
                    proxy.connect(socket_factory, host, port, connectTimeout);
                    io.setInputStream(proxy.getInputStream());
                    io.setOutputStream(proxy.getOutputStream());
                    socket = proxy.getSocket();
                }
            }

            //2025.6.28: 设置tcp协议的socket超时时间
            if (connectTimeout > 0 && socket != null) {
                socket.setSoTimeout(connectTimeout);
            }

            //2025.6.27: 如果创建套接字成功，则设置标志位，表示网络已经连接
            //2025.7.10: tcp协议层的连接已经完成
            isConnected = true;

            log.info("[ssh协议的第一层] === 1.连接建立, Connection established ===");

            //2025.6.27: 将session放到jsch工具类中
            log.info("[ssh协议的第一层] 将session放进连接池(sessionPool)");
            jsch.addSession(this);

            /**
             *  2025.7.10: 第一步：组装ssh协议，发送第一个ssh协议包，告诉服务器端，我的ssh客户端版本信息。
             *  2025.7.10：报文不是加密的，是明文的，可以用wireshark查看
             */
            {
                // Some Cisco devices will miss to read '\n' if it is sent separately.
                byte[] foo = new byte[clientVersion.length + 1];
                System.arraycopy(clientVersion, 0, foo, 0, clientVersion.length);
                foo[foo.length - 1] = (byte) '\n';
                //2025.6.27: 将客户端版本信息写入到网络中
                io.put(foo, 0, foo.length);
            }

            //2025.6.27: 通过死循环来读取网络协议包
            while (true) {
                i = 0;
                j = 0;
                while (i < buf.buffer.length) {
                    //2025.7.10: 读取服务器端的网络协议包
                    j = io.getByte();
                    if (j < 0) break;
                    buf.buffer[i] = (byte) j;
                    i++;
                    if (j == 10) break;
                }
                if (j < 0) {
                    throw new JSchException("connection is closed by foreign host");
                }

                if (buf.buffer[i - 1] == 10) {    // 0x0a
                    i--;
                    if (i > 0 && buf.buffer[i - 1] == 13) {  // 0x0d
                        i--;
                    }
                }

                if (i <= 3 ||
                        ((i != buf.buffer.length) &&
                                (buf.buffer[0] != 'S' || buf.buffer[1] != 'S' ||
                                        buf.buffer[2] != 'H' || buf.buffer[3] != '-'))) {
                    // It must not start with 'SSH-'
                    continue;
                }

                if (i == buf.buffer.length ||
                        i < 7 ||                                      // SSH-1.99 or SSH-2.0
                        (buf.buffer[4] == '1' && buf.buffer[6] != '9')  // SSH-1.5
                ) {
                    throw new JSchException("invalid server's version string");
                }
                break;
            }
            /**
             * 2025.6.28: ssh协议的服务器端版本
             * 2025.7.10：报文不是加密的，是明文的，可以用wireshark查看
             */
            serverVersion = new byte[i];
            System.arraycopy(buf.buffer, 0, serverVersion, 0, i);
            //2025.6.27: 打印日志
            log.info("[ssh协议的第一层] 服务器端版本, Remote version string: " + Util.byte2str(serverVersion));
            log.info("[ssh协议的第一层] 客户端版本，Local version string: " + Util.byte2str(clientVersion));
            log.info("[ssh协议的第一层] 2. 版本协商完毕(包括ssh协议和实现ssh工具的版本)");
            log.info("[ssh协议的第一层] 开始加密算法协商");

            /**
             * 2025.6.30: 密钥交换（初始化密钥）
             * 2025.6.30: 密钥交换（发送密钥）
             *
             * 第1步: 客户端在payload中表明Message Code = 20(Key Exchange Init),
             * 并毫无保留的把自己支持的各种加密算法告诉服务器，同时猜测服务器支持哪些算法。
             *
             * 第2步，服务端把自己的算法列表公布给客户端。
             * 同时双方根据某种约定好的协商机制（大体上就是，选双方都有的，选第一个），
             * 再之后就由客户端开始双方协商好的密钥交换算法。
             *
             * 2025.7.10：报文不是加密的，是明文的，可以用wireshark查看*
             */

            sendKexInit();

            /**
             * 2025.6.27: 读取报文
             * 2025.7.14：获取服务器端支持的加密算法
             */
            buf = read(buf);
            if (buf.getCommand() != SSH_MSG_KEXINIT) {
                in_kex = false;
                throw new JSchException("invalid protocol: " + buf.getCommand());
            }

            log.info("[ssh协议的第一层] 收到服务器返回的加密算法报文, SSH_MSG_KEXINIT received");

            /**
             * 2025.6.30: 密钥交换（接收密钥的初始化设置）
             * 2025.7.10：密钥算法匹配
             * 2025.7.16: 在处理完SSH_MSG_KEXINIT报文后，会接着发送SSH_MSG_KEXDH_INIT报文
             */
            KeyExchange kex = receiveKexInit(buf);
//            log.info("kex:{}", kex.getClass().getName());

            while (true) {
                //2025.7.16: 接收SSH_MSG_KEXDH_INIT报文(Diffie-Hellman 密钥交换)
                buf = read(buf);
//                log.info("buf.command: {}", buf.getCommand());
                if (kex.getState() == buf.getCommand()) {
//                    log.info("kex.getState() == buf.getCommand(), {}", buf.getCommand());
                    kex_start_time = System.currentTimeMillis();
                    log.info("[ssh协议的第一层] 收到服务器端发送回来的SSH_MSG_KEX_ECDH_REPLY");
                    boolean result = kex.next(buf);
                    if (!result) {
                        in_kex = false;
                        throw new JSchException("verify: " + result);
                    }
                } else {
                    in_kex = false;
                    throw new JSchException("invalid protocol(kex): " + buf.getCommand());
                }
                if (kex.getState() == KeyExchange.STATE_END) {
                    log.info("kex.getState() == KeyExchange.STATE_END");
                    break;
                }
            }

            try {
                long tmp = System.currentTimeMillis();
                in_prompt = true;
                checkHost(host, port, kex);
                in_prompt = false;
                kex_start_time += (System.currentTimeMillis() - tmp);
            } catch (JSchException ee) {
                in_kex = false;
                in_prompt = false;
                throw ee;
            }

            /**
             * 2025.7.10: 认证完服务器后，紧接着客户端就会发送一个new key(SSH_MSG_NEWKEYS21)表明接下来要使用对称加密进行通信了。
             * 之后，wireshark就无法抓取到可读包裹了，只能通过ssh -vvv选项学习理解。
             *
             * 2025.7.10: 以下属于扩展协商
             * 在讨论客户端认证流程之前必须提及2018年4月更新的关于扩展协商的相关标准，
             * 提供了一种机制去帮助客户端和服务器在密钥交换完成之后，秘密地交换各自的扩展。
             * 这个标准制定的主要原因，是因为SSH在原本的设计中没有一个良好的机制去确认服务端支持何种公钥认证机制，
             * 尤其是当RSA with SHA-256、SHA-512被广泛使用以后，如果没有一个良好的扩展协商机制，
             * 就无法避免客户端再认证时候的尝试与错误，以及认证错误的惩罚。
             *
             * 2025.7.12: 2016年产的思科交换机不支持扩展协商。导致SSH_MSG_CHANNEL_REQUEST请求报文，
             * 经常返回奇怪的错误报文
             *
             * 2025.7.17: 密钥交换在双方各发送一个 SSH_MSG_NEWKEYS 消息后结束
             */
            log.info("[ssh协议的第一层] 3.算法协商完毕，开始发送加密报文");
            sendNewKeys();

            // receive SSH_MSG_NEWKEYS(21)
            buf = read(buf);
            if (buf.getCommand() == SSH_MSG_NEWKEYS) {

                log.info("[ssh协议的第一层] SSH_MSG_NEWKEYS received");

                /**
                 * 2025.6.30: 扩展协商，发送SHA-256、SHA-512协议
                 */
                receiveNewKeys(buf, kex);
            } else {
                in_kex = false;
                throw new JSchException("invalid protocol(newkyes): " + buf.getCommand());
            }

            try {
                String s = getConfig("MaxAuthTries");
                if (s != null) {
                    max_auth_tries = Integer.parseInt(s);
                }
            } catch (NumberFormatException e) {
                throw new JSchException("MaxAuthTries: " + getConfig("MaxAuthTries"), e);
            }
            log.info("[ssh协议的第一层] 4.密钥交换完毕; 此后数据都以加密方式传输");

            boolean auth = false;
            boolean auth_cancel = false;
//            log.info("[ssh协议的第一层] 扩展协商完成(SHA-256、SHA-512)");

            log.info("[ssh协议的第二层] 5.用户认证开始; 从此时开始，对报文进行加密");
            /**
             * 2025.6.28: 密钥交换完毕，现在进入用户认证环节
             */
            UserAuth ua = null;
            try {
                Class c = Class.forName(getConfig("userauth.none"));
                ua = (UserAuth) (c.newInstance());
            } catch (Exception e) {
                throw new JSchException(e.toString(), e);
            }

            auth = ua.start(this);

            String cmethods = getConfig("PreferredAuthentications");

            String[] cmethoda = Util.split(cmethods, ",");

            String smethods = null;
            if (!auth) {
                smethods = ((UserAuthNone) ua).getMethods();
                if (smethods != null) {
                    smethods = smethods.toLowerCase();
                } else {
                    smethods = cmethods;
                }
            }

            String[] smethoda = Util.split(smethods, ",");

            int methodi = 0;

            loop:
            while (true) {
                int loopCount = 0;

                while (!auth &&
                        cmethoda != null && methodi < cmethoda.length) {
                    loopCount++;

                    String method = cmethoda[methodi++];
                    boolean acceptable = false;
                    for (int k = 0; k < smethoda.length; k++) {
                        if (smethoda[k].equals(method)) {
                            acceptable = true;
                            break;
                        }
                    }
                    if (!acceptable) {
                        continue;
                    }

                    if (JSch.getLogger().isEnabled(Logger.INFO)) {
                        String str = "Authentications that can continue: ";
                        for (int k = methodi - 1; k < cmethoda.length; k++) {
                            str += cmethoda[k];
                            if (k + 1 < cmethoda.length)
                                str += ",";
                        }
                        log.info("{}, 用户认证轮次: {}", str, loopCount);
                        log.info("Next authentication method: " + method);
                    }

                    ua = null;
                    try {
                        Class c = null;
                        if (getConfig("userauth." + method) != null) {
                            c = Class.forName(getConfig("userauth." + method));
                            ua = (UserAuth) (c.newInstance());
                        }
                    } catch (Exception e) {
                        if (JSch.getLogger().isEnabled(Logger.WARN)) {
                            JSch.getLogger().log(Logger.WARN,
                                    "failed to load " + method + " method");
                        }
                    }
                    /**
                     * 2025.6.28: 认证密码的结果
                     */
                    if (ua != null) {
                        auth_cancel = false;
                        try {
                            auth = ua.start(this);
                            log.info("ua:{}, auth:{}", ua, auth);
                            if (auth) {
                                log.info("Authentication succeeded ({}).", method);
                            }
                        } catch (JSchAuthCancelException ee) {
                            auth_cancel = true;
                        } catch (JSchPartialAuthException ee) {
                            String tmp = smethods;
                            smethods = ee.getMethods();
                            smethoda = Util.split(smethods, ",");
                            if (!tmp.equals(smethods)) {
                                methodi = 0;
                            }
                            auth_cancel = false;
                            continue loop;
                        } catch (RuntimeException ee) {
                            throw ee;
                        } catch (JSchException ee) {
                            throw ee;
                        } catch (Exception ee) {
                            if (JSch.getLogger().isEnabled(Logger.WARN)) {
                                JSch.getLogger().log(Logger.WARN,
                                        "an exception during authentication\n" + ee.toString());
                            }
                            break loop;
                        }
                    }
                }
                break;
            }

            /**
             * 2025.6.30: 如果认证不通过，则重试
             */
            if (!auth) {
                if (auth_failures >= max_auth_tries) {
                    if (JSch.getLogger().isEnabled(Logger.INFO)) {
                        JSch.getLogger().log(Logger.INFO,
                                "Login trials exceeds " + max_auth_tries);
                    }
                }
                if (auth_cancel)
                    throw new JSchException("Auth cancel");
                throw new JSchException("Auth fail");
            }

            if (socket != null && (connectTimeout > 0 || timeout > 0)) {
                socket.setSoTimeout(timeout);
            }


            log.info("[ssh协议的第二层] ==== 5.用户认证交换完毕 ====");

            /**
             * 2025.6.30: 认证通过
             */
            isAuthed = true;

            synchronized (lock) {
                if (isConnected) {
                    connectThread = new Thread(this);
                    connectThread.setName("Connect thread " + host + " session");
                    if (daemon_thread) {
                        connectThread.setDaemon(daemon_thread);
                    }
                    /**
                     * 2025.6.30: 开始处理业务数据，参见session.run函数
                     */
                    connectThread.start();

                    log.info("[ssh协议的第三层] 启动处理业务数据, threadName: " + connectThread.getName());

                    requestPortForwarding();
                } else {
                    // The session has been already down and
                    // we don't have to start new thread.
                }
            }
        } catch (Exception e) {
            in_kex = false;
            try {
                if (isConnected) {
                    String message = e.toString();
                    packet.reset();
                    buf.checkFreeSize(1 + 4 * 3 + message.length() + 2 + buffer_margin);
                    buf.putByte((byte) SSH_MSG_DISCONNECT);
                    buf.putInt(3);
                    buf.putString(Util.str2byte(message));
                    buf.putString(Util.str2byte("en"));
                    write(packet);
                }
            } catch (Exception ee) {
            }
            try {
                disconnect();
            } catch (Exception ee) {
            }
            isConnected = false;
            //e.printStackTrace();
            if (e instanceof RuntimeException) throw (RuntimeException) e;
            if (e instanceof JSchException) throw (JSchException) e;
            throw new JSchException("Session.connect: " + e);
        } finally {
            Util.bzero(this.password);
            this.password = null;
        }
    }

    /**
     * 处理kex(密钥交换类)初始化，接收服务器端的加密算法，协商出双方都认可的加密算法
     * <p>
     * 2025.7.15：此时buf中的内容，已经解密完毕，支持做业务操作，也就是密钥算法匹配
     *
     * @param buf
     * @return
     * @throws Exception
     */
    private KeyExchange receiveKexInit(Buffer buf) throws Exception {
        int j = buf.getInt();
        if (j != buf.getLength()) {    // packet was compressed and
            buf.getByte();           // j is the size of deflated packet.
            byteServerSupportAlgorithm = new byte[buf.index - 5];
        } else {
            byteServerSupportAlgorithm = new byte[j - 1 - buf.getByte()];
        }
        /**
         * 2025.7.16: 得到服务器端支持的算法列表byte数组
         */
        System.arraycopy(buf.buffer, buf.s, byteServerSupportAlgorithm, 0, byteServerSupportAlgorithm.length);

        if (!in_kex) {     // We are in rekeying activated by the remote!
            sendKexInit();
        }

        /**
         * 2025.7.16：得到算法协商结果，是一个数组，最大为10
         */
        guess = KeyExchange.guess(byteServerSupportAlgorithm, byteClientSupportAlgorithm);
        if (guess == null) {
            log.error("[receive_kexinit] 协商加密算法失败");
            throw new JSchException("Algorithm negotiation fail");
        }
        log.error("[receive_kexinit] 协商算法完成，guess:{}", guess);
        if (!isAuthed &&
                (guess[KeyExchange.PROPOSAL_ENC_ALGS_CTOS].equals("none") ||
                        (guess[KeyExchange.PROPOSAL_ENC_ALGS_STOC].equals("none")))) {
            throw new JSchException("NONE Cipher should not be chosen before authentification is successed.");
        }

        KeyExchange kex = null;
        try {
            String kexClassName = getConfig(guess[KeyExchange.PROPOSAL_KEX_ALGS]);
            log.info("协商算法完成, 密钥交换类kexClassName:{}", kexClassName);
            Class c = Class.forName(kexClassName);
            kex = (KeyExchange) (c.newInstance());
        } catch (Exception e) {
            throw new JSchException(e.toString(), e);
        }

//        log.info("[receive_kexinit] kex:{}, V_S:{}, V_C:{} I_S:{}, I_C:{}", kex, new String(V_S), new String(V_C), new String(I_S), new String(I_C));
        /**
         * 2025.7.16: 这里会发送报文SSH_MSG_KEXDH_INIT(Diffie-Hellman 密钥交换)
         * 2027.7.16: 密钥交换类初始化
         */
        kex.init(this, serverVersion, clientVersion, byteServerSupportAlgorithm, byteClientSupportAlgorithm);
        return kex;
    }

    private volatile boolean in_kex = false;
    private volatile boolean in_prompt = false;

    public void rekey() throws Exception {
        sendKexInit();
    }

    /**
     * 2025.6.30: 向服务器发送客户端支持的加密算法
     * <p>
     * 2025.7.17: 报文格式
     * byte        SSH_MSG_KEXINIT
     * byte[16]    cookie (random bytes)
     * name-list   kex_algorithms
     * name-list   server_host_key_algorithms
     * name-list   encryption_algorithms_client_to_server
     * name-list   encryption_algorithms_server_to_client
     * name-list   mac_algorithms_client_to_server
     * name-list   mac_algorithms_server_to_client
     * name-list   compression_algorithms_client_to_server
     * name-list   compression_algorithms_server_to_client
     * name-list   languages_client_to_server
     * name-list   languages_server_to_client
     * boolean     first_kex_packet_follows
     * uint32      0(为将来扩展预留)
     * <p>
     * cookie: 一个由发送方生成的随机值。它的作用是使任何一方都不可能对密钥和会话标识符拥有完全决定权。
     * kex_algorithms: 密钥交换算法。
     * server_host_key_algorithms: 受支持的为服务器主机密钥服务的算法的名称列表，按优先级排序。
     * encryption_algorithms: 可接受的对称加密算法(也称为加密器)的名称列表，按优先级排序。
     * mac_algorithms: 可接受的 MAC 算法的名称列表，按优先级排序。
     * compression_algorithms: 可接受的压缩算法的名称列表，按优先级排序。
     * languages: 语言标志的名称列表，按优先级排序。
     * first_kex_packet_follows: 表明是否有一个猜测的密钥交换数据包跟随。
     * <p>
     * 参考文档: https://www.cnblogs.com/dier-gaohe/p/16971894.html
     *
     * @throws Exception
     */
    private void sendKexInit() throws Exception {
        if (in_kex)
            return;

        /**
         * 客户端到服务器端的加密算法
         */
        String cipherc2s = getConfig("cipher.c2s");
//        log.info("[send_kexinit] cipherc2s:{}", cipherc2s);
        /**
         * 服务器端到客户端的加密算法
         */
        String ciphers2c = getConfig("cipher.s2c");
//        log.info("[send_kexinit] ciphers2c:{}", ciphers2c);
        /**
         * 检查加密算法的实现类是否可以实例化，如果可以，则初始化
         */
        String[] not_available_ciphers = checkCiphers(getConfig("CheckCiphers"));
        if (not_available_ciphers != null && not_available_ciphers.length > 0) {
            cipherc2s = Util.diffString(cipherc2s, not_available_ciphers);
            ciphers2c = Util.diffString(ciphers2c, not_available_ciphers);
            if (cipherc2s == null || ciphers2c == null) {
                throw new JSchException("There are not any available ciphers.");
            }
        }

        /**
         * 2025.7.16: 获取支持的加密算法
         */
        String kex = getConfig("kex");
        log.info("[ssh协议的第一层-send_kexinit] 客户端支持的密钥交换算法, kex:{}", kex);
        /**
         * 检查密钥交换类
         */
        String[] not_available_kexes = checkKexes(getConfig("CheckKexes"));
        if (not_available_kexes != null && not_available_kexes.length > 0) {
            kex = Util.diffString(kex, not_available_kexes);
            if (kex == null) {
                throw new JSchException("There are not any available kexes.");
            }
        }

        String server_host_key = getConfig("server_host_key");
        log.info("[ssh协议的第一层-send_kexinit] 客户端的server_host_key:{}", server_host_key);
        /**
         * 检查签名算法是否存在
         */
        String[] not_available_shks =
                checkSignatures(getConfig("CheckSignatures"));
        if (not_available_shks != null && not_available_shks.length > 0) {
            server_host_key = Util.diffString(server_host_key, not_available_shks);
            if (server_host_key == null) {
                throw new JSchException("There are not any available sig algorithm.");
            }
        }

        in_kex = true;
        kex_start_time = System.currentTimeMillis();

        /**
         * 2025.7.15: 开始组装密钥交换的缓冲
         */
        Buffer buf = new Buffer();                // send_kexinit may be invoked
        Packet packet = new Packet(buf);          // by user thread.
        packet.reset();
        buf.putByte((byte) SSH_MSG_KEXINIT);

        /**
         * 2025.6.30: 其中random padding用于补全长度，payload为有效负载
         * 随机数是16位的，所以缓存要skip 16位
         *
         * 2025.7.17：一个由发送方生成的随机值。它的作用是使任何一方都不可能对密钥和会话标识符拥有完全决定权。
         */
        synchronized (random) {
            random.fill(buf.buffer, buf.index, 16);
            buf.skip(16);
        }
        /**
         * 2025.7.16: 密钥交换算法。
         */
        buf.putString(Util.str2byte(kex));
        //2025.7.16: 受支持的为服务器主机密钥服务的算法的名称列表，按优先级排序。
        buf.putString(Util.str2byte(server_host_key));
        //2025.7.16: 可接受的对称加密算法(也称为加密器)的名称列表，按优先级排序。(客户端到服务器端)
        buf.putString(Util.str2byte(cipherc2s));
        //2025.7.16: 可接受的对称加密算法(也称为加密器)的名称列表，按优先级排序。(服务器端到客户端)
        buf.putString(Util.str2byte(ciphers2c));
        //2025.7.16: ,可接受的消息认证算法算法的名称列表，按优先级排序(客户端到服务器端)
        buf.putString(Util.str2byte(getConfig("mac.c2s")));
        //2025.7.16: 可接受的消息认证算法算法的名称列表，按优先级排序(服务器端到客户端)
        buf.putString(Util.str2byte(getConfig("mac.s2c")));
        //2027.7.15: 可接受的压缩算法的名称列表，按优先级排序(客户端到服务器端)
        buf.putString(Util.str2byte(getConfig("compression.c2s")));
        //2027.7.15:  可接受的压缩算法的名称列表，按优先级排序(服务器端到客户端)
        buf.putString(Util.str2byte(getConfig("compression.s2c")));
        //2025.7.16: 语言标志的名称列表，按优先级排序(客户端到服务器端)
        buf.putString(Util.str2byte(getConfig("lang.c2s")));
        //2025.7.16: 语言标志的名称列表，按优先级排序(服务器端到客户端)
        buf.putString(Util.str2byte(getConfig("lang.s2c")));
        //2025.7.17: 表明是否有一个猜测的密钥交换数据包跟随。
        buf.putByte((byte) 0);
        //2025.7.17: 0(为将来扩展预留)
        buf.putInt(0);

        buf.setOffSet(5);
        byteClientSupportAlgorithm = new byte[buf.getLength()];
        /**
         * 2025.7.16：将缓存中的信息，复制到全局变量，保存本地支持的算法列表
         */
        buf.getByte(byteClientSupportAlgorithm);

        write(packet);

        log.info("SSH_MSG_KEXINIT sent, 客户端发送支持的加密算法协商报文");

    }

    /**
     * 2025.7.15: 从这个报文开始，此后的报文都是加密的
     *
     * @throws Exception
     */
    private void sendNewKeys() throws Exception {
        // send SSH_MSG_NEWKEYS(21)
        packet.reset();
        buf.putByte((byte) SSH_MSG_NEWKEYS);
        write(packet);

        log.info("[ssh协议的第一层] SSH_MSG_NEWKEYS sent");

    }

    /**
     * 检查主机、端口、交换算法
     *
     * @param chost
     * @param port
     * @param kex
     * @throws JSchException
     */
    private void checkHost(String chost, int port, KeyExchange kex) throws JSchException {
//        log.info("chost:{}, port:{}, kex:{}", chost, port, kex);
        String shkc = getConfig("StrictHostKeyChecking");

        if (hostKeyAlias != null) {
            chost = hostKeyAlias;
        }

        byte[] K_S = kex.getHostKey();
        String key_type = kex.getKeyType();
        String key_fprint = kex.getFingerPrint();

        if (hostKeyAlias == null && port != 22) {
            chost = ("[" + chost + "]:" + port);
        }

        HostKeyRepository hkr = getHostKeyRepository();

        String hkh = getConfig("HashKnownHosts");
        if (hkh.equals("yes") && (hkr instanceof KnownHosts)) {
            hostkey = ((KnownHosts) hkr).createHashedHostKey(chost, K_S);
        } else {
            hostkey = new HostKey(chost, K_S);
        }

        int i = 0;
        synchronized (hkr) {
            i = hkr.check(chost, K_S);
        }

        boolean insert = false;
        if ((shkc.equals("ask") || shkc.equals("yes")) &&
                i == HostKeyRepository.CHANGED) {
            String file = null;
            synchronized (hkr) {
                file = hkr.getKnownHostsRepositoryID();
            }
            if (file == null) {
                file = "known_hosts";
            }

            boolean b = false;

            if (userinfo != null) {
                String message =
                        "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!\n" +
                                "IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n" +
                                "Someone could be eavesdropping on you right now (man-in-the-middle attack)!\n" +
                                "It is also possible that the " + key_type + " host key has just been changed.\n" +
                                "The fingerprint for the " + key_type + " key sent by the remote host " + chost + " is\n" +
                                key_fprint + ".\n" +
                                "Please contact your system administrator.\n" +
                                "Add correct host key in " + file + " to get rid of this message.";

                if (shkc.equals("ask")) {
                    b = userinfo.promptYesNo(message +
                            "\nDo you want to delete the old key and insert the new key?");
                } else {  // shkc.equals("yes")
                    userinfo.showMessage(message);
                }
            }

            if (!b) {
                throw new JSchException("HostKey has been changed: " + chost);
            }

            synchronized (hkr) {
                hkr.remove(chost,
                        kex.getKeyAlgorithName(),
                        null);
                insert = true;
            }
        }

        if ((shkc.equals("ask") || shkc.equals("yes")) &&
                (i != HostKeyRepository.OK) && !insert) {
            if (shkc.equals("yes")) {
                throw new JSchException("reject HostKey: " + host);
            }
            if (userinfo != null) {
                boolean foo = userinfo.promptYesNo(
                        "The authenticity of host '" + host + "' can't be established.\n" +
                                key_type + " key fingerprint is " + key_fprint + ".\n" +
                                "Are you sure you want to continue connecting?"
                );
                if (!foo) {
                    throw new JSchException("reject HostKey: " + host);
                }
                insert = true;
            } else {
                if (i == HostKeyRepository.NOT_INCLUDED)
                    throw new JSchException("UnknownHostKey: " + host + ". " + key_type + " key fingerprint is " + key_fprint);
                else
                    throw new JSchException("HostKey has been changed: " + host);
            }
        }

        if (shkc.equals("no") &&
                HostKeyRepository.NOT_INCLUDED == i) {
            insert = true;
        }

        if (i == HostKeyRepository.OK) {
            HostKey[] keys =
                    hkr.getHostKey(chost, kex.getKeyAlgorithName());
            String _key = Util.byte2str(Util.toBase64(K_S, 0, K_S.length));
            for (int j = 0; j < keys.length; j++) {
                if (keys[i].getKey().equals(_key) &&
                        keys[j].getMarker().equals("@revoked")) {
                    if (userinfo != null) {
                        userinfo.showMessage(
                                "The " + key_type + " host key for " + host + " is marked as revoked.\n" +
                                        "This could mean that a stolen key is being used to " +
                                        "impersonate this host.");
                    }
                    if (JSch.getLogger().isEnabled(Logger.INFO)) {
                        JSch.getLogger().log(Logger.INFO,
                                "Host '" + host + "' has provided revoked key.");
                    }
                    throw new JSchException("revoked HostKey: " + host);
                }
            }
        }

        if (i == HostKeyRepository.OK &&
                JSch.getLogger().isEnabled(Logger.INFO)) {
            JSch.getLogger().log(Logger.INFO,
                    "Host '" + host + "' is known and matches the " + key_type + " host key");
        }

        if (insert) {
            log.warn("Permanently added '{}' ({}) to the list of known hosts.", host, key_type);
        }

        if (insert) {
            synchronized (hkr) {
                hkr.add(hostkey, userinfo);
            }
        }
    }

//public void start(){ (new Thread(this)).start();  }

    /**
     * 2025.6.28: 建立通道
     * <p>
     * 2025.7.11: 入口函数
     *
     * @param type 类型
     * @return
     * @throws JSchException
     */
    public Channel openChannel(String type) throws JSchException {
        if (!isConnected) {
            throw new JSchException("session is down");
        }
        try {
            Channel channel = Channel.getChannel(type);
            //2025.6.28: 添加通道
            addChannel(channel);
            //2025.6.28: 初始化
            channel.init();
            if (channel instanceof ChannelSession) {
                //2025.6.28: 应用配置
                applyConfigChannel((ChannelSession) channel);
            }
            return channel;
        } catch (Exception e) {
            //e.printStackTrace();
        }
        return null;
    }

    /**
     * 2025.6.28: 对报文进行编码
     * 2025.7.14：这一步的加密确实很重要；像一些老的交换机，只支持OpenSSH7.2，
     * 2025.7.14：那么c2smac的算法跟OpenSSH9.8的完全不同
     * <p>
     * 由于加密算法的不同，导致交换机无法正确加密+解密，进一步导致SSH_MSG_CHANNEL_REQUEST接收报文，无法正确执行
     * <p>
     * 2025.7.15：先压缩，再加密
     *
     * @param packet
     * @throws Exception
     */
    // encode will bin invoked in write with synchronization.
    public void encode(Packet packet) throws Exception {

        SshMsgEnum sshMsgEnum = SshMsgEnum.getByCode((int) packet.getBuffer().getCommand());
        if (sshMsgEnum != null) {
            log.info("[encodeWrite] deflater:{}, c2scipher:{}, c2smac:{}, command:{}", deflater, c2scipher, c2smac, sshMsgEnum.getMessage());
        } else {
            log.info("[encodeWrite] deflater:{}, c2scipher:{}, c2smac:{}, command:{}", deflater, c2scipher, c2smac, packet.getBuffer().getCommand());
        }

        /**
         * 2025.7.14: 压缩
         */
        if (deflater != null) {
            compressLen[0] = packet.buffer.index;
            packet.buffer.buffer = deflater.compress(packet.buffer.buffer,
                    5, compressLen);
            packet.buffer.index = compressLen[0];
        }
        /**
         * 2025.7.15: 加密算法的前置步骤，填充随机数(c2s表示客户端到服务器端)
         */
        if (c2scipher != null) {
            //packet.padding(c2scipher.getIVSize());
            packet.padding(c2scipherSize);
            int pad = packet.buffer.buffer[4];
            synchronized (random) {
                random.fill(packet.buffer.buffer, packet.buffer.index - pad, pad);
            }
        } else {
            packet.padding(8);
        }
        /**
         * 2025.7.15: 消息认证算法(c2s表示客户端到服务器端)
         */
        if (c2smac != null) {
            c2smac.update(seqOutput);
            c2smac.update(packet.buffer.buffer, 0, packet.buffer.index);
            c2smac.doFinal(packet.buffer.buffer, packet.buffer.index);
        }
        /**
         * 2025.7.15: 加密(c2s表示客户端到服务器端)
         */
        if (c2scipher != null) {
            byte[] buf = packet.buffer.buffer;
            c2scipher.update(buf, 0, packet.buffer.index, buf, 0);
        }
        if (c2smac != null) {
            packet.buffer.skip(c2smac.getBlockSize());
        }
    }

    int[] uncompressLen = new int[1];
    int[] compressLen = new int[1];

    private int s2ccipherSize = 8;
    private int c2scipherSize = 8;

    /**
     * 2025.6.27: 读取服务器端发送过来的报文到缓冲中(内存中的数据结构)
     * 2025.7.14：注意解密算法
     * 2025.7.16：在发送sendNewKey之前，报文都是不加密的；
     *
     * @param buf
     * @return
     * @throws Exception
     */
    public Buffer read(Buffer buf) throws Exception {
        log.info("[开始读网络报文] s2ccipher:{}, s2cmac:{}, inflater:{}", s2ccipher, s2cmac, inflater);
        int j = 0;
        while (true) {
            buf.reset();
            io.getByte(buf.buffer, buf.index, s2ccipherSize);
            buf.index += s2ccipherSize;

            //2025.7.15: 服务器到客户端的加密算法
            if (s2ccipher != null) {
                s2ccipher.update(buf.buffer, 0, s2ccipherSize, buf.buffer, 0);
            }
            j = ((buf.buffer[0] << 24) & 0xff000000) |
                    ((buf.buffer[1] << 16) & 0x00ff0000) |
                    ((buf.buffer[2] << 8) & 0x0000ff00) |
                    ((buf.buffer[3]) & 0x000000ff);
            // RFC 4253 6.1. Maximum Packet Length
            if (j < 5 || j > PACKET_MAX_SIZE) {
                startDiscard(buf, s2ccipher, s2cmac, j, PACKET_MAX_SIZE);
            }
            int need = j + 4 - s2ccipherSize;
            //if(need<0){
            //  throw new IOException("invalid data");
            //}
            if ((buf.index + need) > buf.buffer.length) {
                byte[] foo = new byte[buf.index + need];
                //2025.6.27: 数组复制
                System.arraycopy(buf.buffer, 0, foo, 0, buf.index);
                buf.buffer = foo;
            }

            if ((need % s2ccipherSize) != 0) {
                String message = "Bad packet length " + need;
                if (JSch.getLogger().isEnabled(Logger.FATAL)) {
                    JSch.getLogger().log(Logger.FATAL, message);
                }
                startDiscard(buf, s2ccipher, s2cmac, j, PACKET_MAX_SIZE - s2ccipherSize);
            }

            if (need > 0) {
                io.getByte(buf.buffer, buf.index, need);
                buf.index += (need);
                if (s2ccipher != null) {
                    s2ccipher.update(buf.buffer, s2ccipherSize, need, buf.buffer, s2ccipherSize);
                }
            }

            //2025.7.15: 服务器到客户端的消息完整性验证算法
            if (s2cmac != null) {
                s2cmac.update(seqInput);
                s2cmac.update(buf.buffer, 0, buf.index);

                s2cmac.doFinal(s2cmacResult1, 0);
                io.getByte(s2cmacResult2, 0, s2cmacResult2.length);
                if (!java.util.Arrays.equals(s2cmacResult1, s2cmacResult2)) {
                    if (need > PACKET_MAX_SIZE) {
                        throw new IOException("MAC Error");
                    }
                    startDiscard(buf, s2ccipher, s2cmac, j, PACKET_MAX_SIZE - need);
                    continue;
                }
            }

            seqInput++;

            //2025.7.15: 解压
            if (inflater != null) {
                //inflater.uncompress(buf);
                int pad = buf.buffer[4];
                uncompressLen[0] = buf.index - 5 - pad;
                byte[] foo = inflater.uncompress(buf.buffer, 5, uncompressLen);
                if (foo != null) {
                    buf.buffer = foo;
                    buf.index = 5 + uncompressLen[0];
                } else {
                    break;
                }
            }
            //2025.6.27: 读取ssh协议的命令，根据命令做不同的操作
            int type = buf.getCommand() & 0xff;
            //2025.6.27: 如果是断开连接
            if (type == SSH_MSG_DISCONNECT) {
                buf.rewind();
                buf.getInt();
                buf.getShort();
                int reason_code = buf.getInt();
                byte[] description = buf.getString();
                byte[] language_tag = buf.getString();
                throw new JSchException("SSH_MSG_DISCONNECT: " +
                        reason_code +
                        " " + Util.byte2str(description) +
                        " " + Util.byte2str(language_tag));
            } else if (type == SSH_MSG_IGNORE) {
                //2025.6.27: 如果是忽略
            } else if (type == SSH_MSG_UNIMPLEMENTED) {
                //2025.6.27: 如果是未实现
                buf.rewind();
                buf.getInt();
                buf.getShort();
                int reason_id = buf.getInt();
                if (JSch.getLogger().isEnabled(Logger.INFO)) {
                    JSch.getLogger().log(Logger.INFO,
                            "Received SSH_MSG_UNIMPLEMENTED for " + reason_id);
                }
            } else if (type == SSH_MSG_DEBUG) {
                //2025.6.27: 如果是调试
                buf.rewind();
                buf.getInt();
                buf.getShort();
            } else if (type == SSH_MSG_CHANNEL_WINDOW_ADJUST) {
                //2025.6.27: 如果是滑动窗口调整
                buf.rewind();
                buf.getInt();
                buf.getShort();
                Channel c = Channel.getChannel(buf.getInt(), this);
                if (c == null) {
                } else {
                    c.addRemoteWindowSize(buf.getUInt());
                }
            } else if (type == UserAuth.SSH_MSG_USERAUTH_SUCCESS) {
                //2025.6.27: 服务器端发送认证通过报文
                isAuthed = true;
                if (inflater == null && deflater == null) {
                    String method;
                    method = guess[KeyExchange.PROPOSAL_COMP_ALGS_CTOS];
                    initDeflater(method);
                    method = guess[KeyExchange.PROPOSAL_COMP_ALGS_STOC];
                    initInflater(method);
                }
                break;
            } else {
                break;
            }
        }
        buf.rewind();
        log.info("[读取网络报文完毕] s2ccipher:{}, s2cmac:{}, inflater:{}, command:{}", s2ccipher, s2cmac, inflater, buf.getCommand());
        return buf;
    }

    /**
     * 2025.6.27: 开始丢弃部分
     *
     * @param buf
     * @param cipher
     * @param mac
     * @param packetLength
     * @param discard
     * @throws JSchException
     * @throws IOException
     */
    private void startDiscard(Buffer buf, Cipher cipher, MAC mac,
                              int packetLength, int discard) throws JSchException, IOException {
        MAC discard_mac = null;

        if (!cipher.isCBC()) {
            throw new JSchException("Packet corrupt");
        }

        if (packetLength != PACKET_MAX_SIZE && mac != null) {
            discard_mac = mac;
        }

        discard -= buf.index;

        while (discard > 0) {
            buf.reset();
            int len = discard > buf.buffer.length ? buf.buffer.length : discard;
            io.getByte(buf.buffer, 0, len);
            if (discard_mac != null) {
                discard_mac.update(buf.buffer, 0, len);
            }
            discard -= len;
        }

        if (discard_mac != null) {
            discard_mac.doFinal(buf.buffer, 0);
        }

        throw new JSchException("Packet corrupt");
    }

    byte[] getSessionId() {
        return session_id;
    }

    private void receiveNewKeys(Buffer buf, KeyExchange kex) throws Exception {
//        log.info("[receiveNewKeys] kex类:{} ", kex.getClass().getName());
        updateKeys(kex);
        in_kex = false;
    }

    private void updateKeys(KeyExchange kex) throws Exception {
        log.info("[updateKeys] kexClassName:{}", kex.getClass().getName());
        byte[] K = kex.getK();
        byte[] H = kex.getH();
        HASH hash = kex.getHash();

        if (session_id == null) {
            session_id = new byte[H.length];
            System.arraycopy(H, 0, session_id, 0, H.length);
        }

        buf.reset();
        buf.putMPInt(K);
        buf.putByte(H);
        buf.putByte((byte) 0x41);
        buf.putByte(session_id);
        hash.update(buf.buffer, 0, buf.index);
        IVc2s = hash.digest();

        int j = buf.index - session_id.length - 1;

        buf.buffer[j]++;
        hash.update(buf.buffer, 0, buf.index);
        IVs2c = hash.digest();

        buf.buffer[j]++;
        hash.update(buf.buffer, 0, buf.index);
        Ec2s = hash.digest();

        buf.buffer[j]++;
        hash.update(buf.buffer, 0, buf.index);
        Es2c = hash.digest();

        buf.buffer[j]++;
        hash.update(buf.buffer, 0, buf.index);
        MACc2s = hash.digest();

        buf.buffer[j]++;
        hash.update(buf.buffer, 0, buf.index);
        MACs2c = hash.digest();

        try {
            Class c;
            String method;

            method = guess[KeyExchange.PROPOSAL_ENC_ALGS_STOC];
            c = Class.forName(getConfig(method));
            s2ccipher = (Cipher) (c.newInstance());
            while (s2ccipher.getBlockSize() > Es2c.length) {
                buf.reset();
                buf.putMPInt(K);
                buf.putByte(H);
                buf.putByte(Es2c);
                hash.update(buf.buffer, 0, buf.index);
                byte[] foo = hash.digest();
                byte[] bar = new byte[Es2c.length + foo.length];
                System.arraycopy(Es2c, 0, bar, 0, Es2c.length);
                System.arraycopy(foo, 0, bar, Es2c.length, foo.length);
                Es2c = bar;
            }
            s2ccipher.init(Cipher.DECRYPT_MODE, Es2c, IVs2c);
            log.info("[receiveNewKeys] 初始化s2c加密算法工具类完毕");
            s2ccipherSize = s2ccipher.getIVSize();

            method = guess[KeyExchange.PROPOSAL_MAC_ALGS_STOC];
            c = Class.forName(getConfig(method));

            s2cmac = (MAC) (c.newInstance());
            MACs2c = expandKey(buf, K, H, MACs2c, hash, s2cmac.getBlockSize());
            s2cmac.init(MACs2c);
            log.info("[receiveNewKeys] 初始化服务器端到客户端的消息认证算法完毕");
            //mac_buf=new byte[s2cmac.getBlockSize()];
            s2cmacResult1 = new byte[s2cmac.getBlockSize()];
            s2cmacResult2 = new byte[s2cmac.getBlockSize()];

            method = guess[KeyExchange.PROPOSAL_ENC_ALGS_CTOS];
            c = Class.forName(getConfig(method));
            c2scipher = (Cipher) (c.newInstance());
            while (c2scipher.getBlockSize() > Ec2s.length) {
                buf.reset();
                buf.putMPInt(K);
                buf.putByte(H);
                buf.putByte(Ec2s);
                hash.update(buf.buffer, 0, buf.index);
                byte[] foo = hash.digest();
                byte[] bar = new byte[Ec2s.length + foo.length];
                System.arraycopy(Ec2s, 0, bar, 0, Ec2s.length);
                System.arraycopy(foo, 0, bar, Ec2s.length, foo.length);
                Ec2s = bar;
            }
            c2scipher.init(Cipher.ENCRYPT_MODE, Ec2s, IVc2s);
            log.info("[receiveNewKeys] 初始化c2s加密算法工具类完毕");
            c2scipherSize = c2scipher.getIVSize();

            method = guess[KeyExchange.PROPOSAL_MAC_ALGS_CTOS];
            c = Class.forName(getConfig(method));
            c2smac = (MAC) (c.newInstance());
            MACc2s = expandKey(buf, K, H, MACc2s, hash, c2smac.getBlockSize());
            c2smac.init(MACc2s);
            log.info("[receiveNewKeys] 初始化c2s的消息认证算法完毕");

            method = guess[KeyExchange.PROPOSAL_COMP_ALGS_CTOS];
            initDeflater(method);
            log.info("[receiveNewKeys] 初始化压缩器(c2s)，压缩发往服务器端的报文");

            method = guess[KeyExchange.PROPOSAL_COMP_ALGS_STOC];
            initInflater(method);
            log.info("[receiveNewKeys] 初始化解压器(s2c), 解压服务器端发来的报文");
        } catch (Exception e) {
            if (e instanceof JSchException)
                throw e;
            throw new JSchException(e.toString(), e);
        }
    }


    /*
     * RFC 4253  7.2. Output from Key Exchange
     * If the key length needed is longer than the output of the HASH, the
     * key is extended by computing HASH of the concatenation of K and H and
     * the entire key so far, and appending the resulting bytes (as many as
     * HASH generates) to the key.  This process is repeated until enough
     * key material is available; the key is taken from the beginning of
     * this value.  In other words:
     *   K1 = HASH(K || H || X || session_id)   (X is e.g., "A")
     *   K2 = HASH(K || H || K1)
     *   K3 = HASH(K || H || K1 || K2)
     *   ...
     *   key = K1 || K2 || K3 || ...
     */
    private byte[] expandKey(Buffer buf, byte[] K, byte[] H, byte[] key,
                             HASH hash, int required_length) throws Exception {
        byte[] result = key;
        int size = hash.getBlockSize();
        while (result.length < required_length) {
            buf.reset();
            buf.putMPInt(K);
            buf.putByte(H);
            buf.putByte(result);
            hash.update(buf.buffer, 0, buf.index);
            byte[] tmp = new byte[result.length + size];
            System.arraycopy(result, 0, tmp, 0, result.length);
            System.arraycopy(hash.digest(), 0, tmp, result.length, size);
            Util.bzero(result);
            result = tmp;
        }
        return result;
    }

    /**
     * 2025.6.28: 往网络输出报文
     *
     * @param packet
     * @param c
     * @param length
     * @throws Exception
     */
    /*public*/ /*synchronized*/ void write(Packet packet, Channel c, int length) throws Exception {
        long t = getTimeout();
        /**
         * 2025.6.28: 通过死循环来组装报文
         */
        while (true) {
            if (in_kex) {
                if (t > 0L && (System.currentTimeMillis() - kex_start_time) > t) {
                    throw new JSchException("timeout in waiting for rekeying process.");
                }
                try {
                    Thread.sleep(10);
                } catch (java.lang.InterruptedException e) {
                }
                ;
                continue;
            }
            synchronized (c) {

                if (c.rwsize < length) {
                    try {
                        c.notifyme++;
                        c.wait(100);
                    } catch (java.lang.InterruptedException e) {
                    } finally {
                        c.notifyme--;
                    }
                }

                if (in_kex) {
                    continue;
                }

                if (c.rwsize >= length) {
                    c.rwsize -= length;
                    break;
                }

            }
            if (c.close || !c.isConnected()) {
                throw new IOException("channel is broken");
            }

            boolean sendit = false;
            int s = 0;
            byte command = 0;
            int recipient = -1;
            synchronized (c) {
                if (c.rwsize > 0) {
                    long len = c.rwsize;
                    if (len > length) {
                        len = length;
                    }
                    if (len != length) {
                        s = packet.shift((int) len,
                                (c2scipher != null ? c2scipherSize : 8),
                                (c2smac != null ? c2smac.getBlockSize() : 0));
                    }
                    command = packet.buffer.getCommand();
                    recipient = c.getRecipient();
                    length -= len;
                    c.rwsize -= len;
                    sendit = true;
                }
            }
            if (sendit) {
                /**
                 * 2025.6.28: 组装完毕，开始往网络输出报文
                 */
                encodeWrite(packet);
                if (length == 0) {
                    return;
                }
                packet.unshift(command, recipient, s, length);
            }

            synchronized (c) {
                if (in_kex) {
                    continue;
                }
                if (c.rwsize >= length) {
                    c.rwsize -= length;
                    break;
                }

            }
        }
        /**
         * 2025.6.28: 组装完毕，开始往网络输出报文
         */
        encodeWrite(packet);
    }

    /**
     * 2025.6.27: 向ssh服务器发送报文（存在加密操作）
     *
     * @param packet
     * @throws Exception
     */
    public void write(Packet packet) throws Exception {
        long t = getTimeout();
        while (in_kex) {
            if (t > 0L &&
                    (System.currentTimeMillis() - kex_start_time) > t &&
                    !in_prompt
            ) {
                throw new JSchException("timeout in waiting for rekeying process.");
            }
            byte command = packet.buffer.getCommand();
            log.info("[session.write] command:{} ", command);
            if (command == SSH_MSG_KEXINIT ||
                    command == SSH_MSG_NEWKEYS ||
                    command == SSH_MSG_KEXDH_INIT ||
                    command == SSH_MSG_KEXDH_REPLY ||
                    command == SSH_MSG_KEX_DH_GEX_GROUP ||
                    command == SSH_MSG_KEX_DH_GEX_INIT ||
                    command == SSH_MSG_KEX_DH_GEX_REPLY ||
                    command == SSH_MSG_KEX_DH_GEX_REQUEST ||
                    command == SSH_MSG_DISCONNECT) {
                break;
            }
            try {
                Thread.sleep(10);
            } catch (java.lang.InterruptedException e) {
            }
            ;
        }
        //2025.6.28: 对报文进行加密，然后发送给服务器端
        encodeWrite(packet);
    }

    /**
     * 2025.6.28: 向服务器端发送报文
     * 2025.7.10: 该函数存在加密报文操作，只用能ssh -vvv查看，无法用wireshark查看
     *
     * @param packet
     * @throws Exception
     */
    private void encodeWrite(Packet packet) throws Exception {
//        JSch.logInfo("===write packet===, threadName: " + Thread.currentThread().getName());
        synchronized (lock) {
            //2025.6.28: 对报文进行加密，只用能ssh -vvv查看，无法用wireshark查看
            encode(packet);
            if (io != null) {
                //2025.6.28: 通过socket来往网络中写包
                io.put(packet);
                seqOutput++;
            }
        }
    }

    Runnable thread;

    /**
     * 2025.6.27: 通过线程来与ssh服务器对话
     * 2025.6.30：处理业务数据
     * 2025.6.30: 这里只处理ssh协议第三层的信息(connect函数处理第一层和第二层)
     * 2025.7.15：这一块的代码问题不用查。逻辑简单清晰。如果问题出现在这里，那应该去查加解密部分的问题
     */
    public void run() {
        log.info("[ssh协议的第三层] 开始处理ssh协议的第三层，也就是ssh服务器发送过来的报文");

        thread = this;

        byte[] foo;
        //2025.6.27: 网络协议缓存
        Buffer buf = new Buffer();
        //2025.6.27: ssh协议报文包
        Packet packet = new Packet(buf);
        int i = 0;
        Channel channel;
        int[] start = new int[1];
        int[] length = new int[1];
        KeyExchange kex = null;

        int stimeout = 0;
        try {
            while (isConnected &&
                    thread != null) {
                try {
                    //2025.6.27: 读取网络数据到缓存中
                    buf = read(buf);
                    stimeout = 0;
                } catch (InterruptedIOException/*SocketTimeoutException*/ ee) {
                    if (!in_kex && stimeout < serverAliveCountMax) {
                        sendKeepAliveMsg();
                        stimeout++;
                        continue;
                    } else if (in_kex && stimeout < serverAliveCountMax) {
                        stimeout++;
                        continue;
                    }
                    throw ee;
                }

                int msgType = buf.getCommand() & 0xff;

                if (kex != null && kex.getState() == msgType) {
                    kex_start_time = System.currentTimeMillis();
                    boolean result = kex.next(buf);
                    if (!result) {
                        throw new JSchException("verify: " + result);
                    }
                    continue;
                }

                switch (msgType) {
                    //2025.6.27: [加密层] Key Exchange初始化报文
                    case SSH_MSG_KEXINIT:
                        log.info("[ssh协议的第一层] 接收报文，SSH_MSG_KEXINIT");
                        kex = receiveKexInit(buf);
                        break;
                    //2025.7.10: [加密层] 扩展协商报文
                    case SSH_MSG_NEWKEYS:
                        log.info("[ssh协议的第一层] 接收报文，扩展协商SSH_MSG_NEWKEYS");
                        //2025.6.27: 如果是新的keys
                        sendNewKeys();
                        receiveNewKeys(buf, kex);
                        kex = null;
                        break;
                    //2025.7.10: [应用层] 通道数据
                    case SSH_MSG_CHANNEL_DATA:
                        /**
                         * 2025.6.30: 接收方接收到这个消息以后，接收方可以根据这个给定的数增加窗口的大小，
                         * 2025.6.30: 不论如何窗口最大为2^32-1字节。具体的数据，则通过下面的消息发送
                         */
                        log.info("[ssh协议的第三层] 接收报文，SSH_MSG_CHANNEL_DATA");
                        //2025.6.27: 如果是新的通道数据
                        buf.getInt();
                        buf.getByte();
                        buf.getByte();
                        //2025.7.10: 接收端的通道编号
                        i = buf.getInt();
                        channel = Channel.getChannel(i, this);
                        foo = buf.getString(start, length);
//                        log.info("SSH_MSG_CHANNEL_DATA, 接收到数据:{}", foo);
                        if (channel == null) {
                            break;
                        }

                        if (length[0] == 0) {
                            break;
                        }

                        try {
                            channel.write(foo, start[0], length[0]);
                        } catch (Exception e) {
                            try {
                                channel.disconnect();
                            } catch (Exception ee) {
                            }
                            break;
                        }
                        int len = length[0];
                        channel.setLocalWindowSize(channel.lwsize - len);
                        if (channel.lwsize < channel.lwsize_max / 2) {
                            packet.reset();
                            buf.putByte((byte) SSH_MSG_CHANNEL_WINDOW_ADJUST);
                            buf.putInt(channel.getRecipient());
                            buf.putInt(channel.lwsize_max - channel.lwsize);
                            synchronized (channel) {
                                if (!channel.close)
                                    write(packet);
                            }
                            channel.setLocalWindowSize(channel.lwsize_max);
                        }
                        break;
                    //2025.7.10: [应用层] 通道扩展数据
                    case SSH_MSG_CHANNEL_EXTENDED_DATA:
                        log.info("[ssh协议的第三层] 接收报文，SSH_MSG_CHANNEL_EXTENDED_DATA, thread:" + Thread.currentThread().getName());
                        //2025.6.27: 如果是新的通道扩展数据
                        buf.getInt();
                        buf.getShort();
                        //2025.7.10: 接收端的通道编号
                        i = buf.getInt();
                        channel = Channel.getChannel(i, this);
                        buf.getInt();                   // data_type_code == 1
                        foo = buf.getString(start, length);
                        if (channel == null) {
                            break;
                        }

                        if (length[0] == 0) {
                            break;
                        }
//                        JSch.logInfo("[ssh协议的第三层] 扩展输出: "
////                                + Util.byte2str(foo)
//                        );
                        channel.write_ext(foo, start[0], length[0]);

                        len = length[0];
                        channel.setLocalWindowSize(channel.lwsize - len);
                        if (channel.lwsize < channel.lwsize_max / 2) {
                            packet.reset();
                            buf.putByte((byte) SSH_MSG_CHANNEL_WINDOW_ADJUST);
                            buf.putInt(channel.getRecipient());
                            buf.putInt(channel.lwsize_max - channel.lwsize);
                            synchronized (channel) {
                                if (!channel.close)
                                    write(packet);
                            }
                            channel.setLocalWindowSize(channel.lwsize_max);
                        }
                        break;
                    //2025.7.10: [应用层] 窗口调整
                    case SSH_MSG_CHANNEL_WINDOW_ADJUST:
                        /**
                         * 2025.6.30: 同时协议规定双方都可以通过下面的消息对窗口作出调整。
                         */
                        log.info("[ssh协议的第三层] 接收报文，SSH_MSG_CHANNEL_WINDOW_ADJUST");
                        //2025.6.27: 如果是通道窗口调整
                        buf.getInt();
                        buf.getShort();
                        //2025.7.10: 接收端的通道编号
                        i = buf.getInt();
                        channel = Channel.getChannel(i, this);
                        if (channel == null) {
                            break;
                        }
                        channel.addRemoteWindowSize(buf.getUInt());
                        break;
                    //2025.7.10: [应用层] 关闭通道(EOF)
                    case SSH_MSG_CHANNEL_EOF:
                        /**
                         * 2025.6.30: 当任意一方不在往Channel发送更多数据的时候，它应该发送一个SSH_MSG_CHANNEL_EOF消息。
                         */
                        log.info("[ssh协议的第三层] 接收报文，SSH_MSG_CHANNEL_EOF");
                        //2025.6.27: 如果是通道结束位
                        buf.getInt();
                        buf.getShort();
                        //2025.7.10: 接收端的通道编号
                        i = buf.getInt();
                        channel = Channel.getChannel(i, this);
                        if (channel != null) {
                            channel.eof_remote();
                        }
                        break;
                    //2025.7.10: [应用层] 关闭通道(Close)
                    case SSH_MSG_CHANNEL_CLOSE:
                        /**
                         * 2025.6.30: 当任意一方希望结束Channel时，则应该发送SSH_MSG_CHANNEL_CLOSE
                         */
                        log.info("[ssh协议的第三层] 接收报文，SSH_MSG_CHANNEL_CLOSE");
                        //2025.6.27: 如果是通道关闭
                        buf.getInt();
                        buf.getShort();
                        //2025.7.10: 接收端的通道编号
                        i = buf.getInt();
                        channel = Channel.getChannel(i, this);
                        if (channel != null) {
//	      channel.close();
                            channel.disconnect();
                        }
                        break;
                    //2025.7.10: [应用层] 打开通道确认
                    case SSH_MSG_CHANNEL_OPEN_CONFIRMATION:
                        /**
                         * 2025.6.30: 对端收到消息后需要作出决定是否同意开启一个Channel，
                         * 2025.6.30: 使用SSH_MSG_CHANNEL_OPEN_CONFIRMATION或SSH_MSG_CHANNEL_OPEN_FAILURE响应消息。
                         */
//                        JSch.logInfo("[ssh协议的第三层] 接收报文，SSH_MSG_CHANNEL_OPEN_CONFIRMATION");
                        //2025.6.27: 如果是通道打开确认
                        buf.getInt();
                        buf.getShort();
                        //2025.7.10: 接收端的通道编号
                        i = buf.getInt();
                        channel = Channel.getChannel(i, this);
                        log.info("[ssh协议的第三层] 接收报文，SSH_MSG_CHANNEL_OPEN_CONFIRMATION, 通道编号: " + i);
                        //2025.7.10: 发送端的通道编号
                        int r = buf.getInt();
                        long rws = buf.getUInt();
                        int rps = buf.getInt();
                        if (channel != null) {
                            channel.setRemoteWindowSize(rws);
                            channel.setRemotePacketSize(rps);
                            channel.open_confirmation = true;
                            channel.setRecipient(r);
                        }
                        break;
                    //2025.7.10: [应用层] 打开通道失败
                    case SSH_MSG_CHANNEL_OPEN_FAILURE:
                        /**
                         * 2025.6.30: 比如被请求开启Channel的一方不支持标注的channel type，
                         * 2025.6.30: 那它将简单地回应SSH_MSG_CHANNEL_OPEN_FAILURE
                         */
                        log.info("[ssh协议的第三层] 接收报文，SSH_MSG_CHANNEL_OPEN_FAILURE");
                        //2025.6.27: 如果是通道打开失败
                        buf.getInt();
                        buf.getShort();
                        //2025.7.10: 接收端的通道编号
                        i = buf.getInt();
                        channel = Channel.getChannel(i, this);
                        ///2025.6.27: 如果通道不为空，则设置退出状态位
                        if (channel != null) {
                            int reasonCode = buf.getInt();
                            channel.setExitStatus(reasonCode);
                            String errorReason = new String(buf.getString());
                            log.info("[ssh协议的第三层] 接收报文，SSH_MSG_CHANNEL_OPEN_FAILURE11, exitStatus: "
                                    + channel.getExitStatus()
                                    + ", errorReason:" + errorReason);
                            //2025.6.28: 关闭通道

                            channel.close = true;
                            channel.eof_remote = true;
                            channel.setRecipient(0);
                        }
                        break;
                    //2025.7.10: [应用层] 打开通道申请
                    case SSH_MSG_CHANNEL_REQUEST:
                        /**
                         * 2025.6.30: 通过如下消息可以让服务器为Session分配一个虚拟终端，
                         * character/row的优先级相比于pixels更高，除非他们被设置成0
                         *
                         * 2025.7.14: 这一步出问题，可能是因为加密算法，客户端+服务器不一致导致
                         */
                        log.info("[ssh协议的第三层] 接收报文，SSH_MSG_CHANNEL_REQUEST");
                        //2025.6.27: 如果是通道打开请求
                        buf.getInt();
                        buf.getShort();
                        //2025.7.10: 接收端的通道编号
                        i = buf.getInt();
                        //读取字符串("exit-status")
                        foo = buf.getString();
                        //读取是否需要回复
                        boolean reply = (buf.getByte() != 0);
                        channel = Channel.getChannel(i, this);
                        ///2025.6.27: 如果通道不为空，
                        if (channel != null) {
                            byte reply_type = (byte) SSH_MSG_CHANNEL_FAILURE;
                            //且如果返回的是exit-status，则设置退出状态码
                            if ((Util.byte2str(foo)).equals("exit-status")) {
                                //读取退出的状态码
                                //2025.7.14: exit-status=0是正常退出。其他值，都是不正常退出
                                i = buf.getInt();             // exit-status
                                log.info("[ssh协议的第三层] 接收报文，SSH_MSG_CHANNEL_REQUEST, 判断退出状态，状态码: " + i + ", reply:" + reply);
                                channel.setExitStatus(i);
                                reply_type = (byte) SSH_MSG_CHANNEL_SUCCESS;
                            }
                            if (reply) {
                                packet.reset();
                                buf.putByte(reply_type);
                                buf.putInt(channel.getRecipient());
                                write(packet);
                            }
                        } else {
                        }
                        break;
                    //2025.7.10: [应用层] 打开通道
                    case SSH_MSG_CHANNEL_OPEN:
                        /**
                         * 2025.6.30: 使用如下消息打开一个Session Channel
                         * 2025.7.2: 当任意一端想要新建一个Channel时，它首先要给Channel分配一个本端的数字标识。
                         * 2025.7.2: 然后将下面的消息发送给对端，这个消息包括了本（发送）端标识、初始化窗口大小等
                         */
                        log.info("[ssh协议的第三层] 接收报文，SSH_MSG_CHANNEL_OPEN");
                        //2025.6.27: 如果是通道打开
                        buf.getInt();
                        buf.getShort();
                        foo = buf.getString();
                        String ctyp = Util.byte2str(foo);
                        if (!"forwarded-tcpip".equals(ctyp) &&
                                !("x11".equals(ctyp) && x11Forwarding) &&
                                !("auth-agent@openssh.com".equals(ctyp) && agentForwarding)) {
                            packet.reset();
                            buf.putByte((byte) SSH_MSG_CHANNEL_OPEN_FAILURE);
                            buf.putInt(buf.getInt());
                            buf.putInt(Channel.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED);
                            buf.putString(Util.empty);
                            buf.putString(Util.empty);
                            write(packet);
                        } else {
                            channel = Channel.getChannel(ctyp);
                            addChannel(channel);
                            channel.getData(buf);
                            channel.init();

                            Thread tmp = new Thread(channel);
                            tmp.setName("Channel " + ctyp + " " + host);
                            if (daemon_thread) {
                                tmp.setDaemon(daemon_thread);
                            }
                            tmp.start();
                        }
                        break;
                    //2025.7.10: [应用层] 打开通道成功
                    case SSH_MSG_CHANNEL_SUCCESS:
                        /**
                         * 2025.6.30: 如果want reply被设置成FALSE，不会有响应被回复给请求端。
                         * 2025.6.30: 否则，响应可能包括SSH_MSG_CHANNEL_SUCCESS、SSH_MSG_CHANNEL_FAILURE或者要求继续提供信息的消息
                         */
                        log.info("[ssh协议的第三层] 接收报文，SSH_MSG_CHANNEL_SUCCESS");
                        //2025.6.27: 如果是通道成功
                        buf.getInt();
                        buf.getShort();
                        //2025.7.10: 接收端的通道编号
                        i = buf.getInt();
                        channel = Channel.getChannel(i, this);
                        if (channel == null) {
                            break;
                        }
                        log.info("[ssh协议的第三层] 接收报文，消息通道成功: " + i);
                        channel.reply = 1;
                        break;
                    //2025.7.10: [应用层] 通道失败
                    case SSH_MSG_CHANNEL_FAILURE:
                        /**
                         * 2025.6.30: 如果want reply被设置成FALSE，不会有响应被回复给请求端。
                         * 2025.6.30: 否则，响应可能包括SSH_MSG_CHANNEL_SUCCESS、SSH_MSG_CHANNEL_FAILURE或者要求继续提供信息的消息
                         */
                        log.info("[ssh协议的第三层] 接收报文，SSH_MSG_CHANNEL_FAILURE");
                        //2025.6.27: 如果是通道失败
                        buf.getInt();
                        buf.getShort();
                        //2025.7.10: 接收端的通道编号
                        i = buf.getInt();
                        channel = Channel.getChannel(i, this);
                        if (channel == null) {
                            break;
                        }
                        channel.reply = 0;
                        break;
                    //2025.7.10: [应用层] 全局配置请求
                    case SSH_MSG_GLOBAL_REQUEST:
                        /**
                         * 2025.6.30: 协议规定了许多种可以影响远端全局而独立于任何Channel的请求，
                         * 2025.6.30: 一个例子就是唯一个特定的端口请求TCP/IP转发。
                         * 2025.6.30: 双方中的任意一方都有可能在任意时间发送全局请求，接收方必须做出合理的响应，其格式如下
                         */
                        log.info("[ssh协议的第三层] 接收报文，SSH_MSG_GLOBAL_REQUEST");
                        //2025.6.27: 如果是全局请求
                        buf.getInt();
                        buf.getShort();
                        foo = buf.getString();       // request name
                        reply = (buf.getByte() != 0);
                        if (reply) {
                            packet.reset();
                            buf.putByte((byte) SSH_MSG_REQUEST_FAILURE);
                            write(packet);
                        }
                        break;
                    //2025.7.10: [应用层] 请求失败
                    case SSH_MSG_REQUEST_FAILURE:
                        log.info("[ssh协议的第三层] 接收报文，SSH_MSG_REQUEST_FAILURE");
                        buf.getInt();
                        buf.getShort();
                        //2025.7.10: 接收端的通道编号
                        i = buf.getInt();
//                        foo = buf.getString();
                        channel = Channel.getChannel(i, this);
                        if (channel != null) {
                            log.info("[ssh协议的第三层] 接收报文，SSH_MSG_REQUEST_FAILURE11");
                        } else {
                            log.info("[ssh协议的第三层] 接收报文，SSH_MSG_REQUEST_FAILURE12, 通道编号: " + i);
                        }
                        //2025.7.10: [应用层] 请求成功
                    case SSH_MSG_REQUEST_SUCCESS:
                        log.info("[ssh协议的第三层] 接收报文，SSH_MSG_REQUEST_SUCCESS");
                        //2025.6.27: 如果是请求成功
                        Thread t = grr.getThread();
                        if (t != null) {
                            grr.setReply(msgType == SSH_MSG_REQUEST_SUCCESS ? 1 : 0);
                            if (msgType == SSH_MSG_REQUEST_SUCCESS && grr.getPort() == 0) {
                                buf.getInt();
                                buf.getShort();
                                grr.setPort(buf.getInt());
                            }
                            t.interrupt();
                        }
                        break;
                    //2025.7.10: [应用层] 其它
                    default:
                        log.info("[ssh协议的第三层] 接收报文，Unknown SSH message");
                        throw new IOException("Unknown SSH message type " + msgType);
                }
            }
        } catch (Exception e) {
            in_kex = false;
            /**
             * 2025.6.28: 捕获到异常, 则打印日志
             */
//            if (JSch.getLogger().isEnabled(Logger.INFO)) {
//                JSch.getLogger().log(Logger.INFO,
//                        "Caught an exception, leaving main loop due to " + e.getMessage());
//            }
            log.error("Caught an exception, leaving main loop", e);
        }
        try {
            disconnect();
        } catch (NullPointerException e) {
        } catch (Exception e) {
        }
        isConnected = false;
    }

    /**
     * 2025.6.27: 断开连接
     * 2025.6.30: 与服务器端断开连接（属于tcp协议层，还没有到ssh协议这里）
     */
    public void disconnect() {
        if (!isConnected) return;
//        if (JSch.getLogger().isEnabled(Logger.INFO)) {
//            JSch.getLogger().log(Logger.INFO,
//                    "Disconnecting from " + host + " port " + port);
//        }
        log.info("Disconnecting from {} port {}", host, port);

        Channel.disconnect(this);

        isConnected = false;

        PortWatcher.delPort(this);
        ChannelForwardedTCPIP.delPort(this);
        ChannelX11.removeFakedCookie(this);

        synchronized (lock) {
            if (connectThread != null) {
                Thread.yield();
                connectThread.interrupt();
                connectThread = null;
            }
        }
        thread = null;
        try {
            //2025.6.27: 关闭IO
            if (io != null) {
                if (io.in != null) io.in.close();
                if (io.out != null) io.out.close();
                if (io.out_ext != null) io.out_ext.close();
            }
            if (proxy == null) {
                //2025.6.27: 关闭套接字
                if (socket != null)
                    socket.close();
            } else {
                synchronized (proxy) {
                    proxy.close();
                }
                proxy = null;
            }
        } catch (Exception e) {
        }
        io = null;
        socket = null;
        jsch.removeSession(this);
    }

    /**
     * Registers the local port forwarding for loop-back interface.
     * If <code>lport</code> is <code>0</code>, the tcp port will be allocated.
     *
     * @param lport local port for local port forwarding
     * @param host  host address for local port forwarding
     * @param rport remote port number for local port forwarding
     * @return an allocated local TCP port number
     * @see #setPortForwardingL(String bind_address, int lport, String host, int rport, ServerSocketFactory ssf, int connectTimeout)
     */
    public int setPortForwardingL(int lport, String host, int rport) throws JSchException {
        return setPortForwardingL("127.0.0.1", lport, host, rport);
    }

    /**
     * Registers the local port forwarding.  If <code>bind_address</code> is an empty string
     * or '*', the port should be available from all interfaces.
     * If <code>bind_address</code> is <code>"localhost"</code> or
     * <code>null</code>, the listening port will be bound for local use only.
     * If <code>lport</code> is <code>0</code>, the tcp port will be allocated.
     *
     * @param bind_address bind address for local port forwarding
     * @param lport        local port for local port forwarding
     * @param host         host address for local port forwarding
     * @param rport        remote port number for local port forwarding
     * @return an allocated local TCP port number
     * @see #setPortForwardingL(String bind_address, int lport, String host, int rport, ServerSocketFactory ssf, int connectTimeout)
     */
    public int setPortForwardingL(String bind_address, int lport, String host, int rport) throws JSchException {
        return setPortForwardingL(bind_address, lport, host, rport, null);
    }

    /**
     * Registers the local port forwarding.
     * If <code>bind_address</code> is an empty string or <code>"*"</code>,
     * the port should be available from all interfaces.
     * If <code>bind_address</code> is <code>"localhost"</code> or
     * <code>null</code>, the listening port will be bound for local use only.
     * If <code>lport</code> is <code>0</code>, the tcp port will be allocated.
     *
     * @param bind_address bind address for local port forwarding
     * @param lport        local port for local port forwarding
     * @param host         host address for local port forwarding
     * @param rport        remote port number for local port forwarding
     * @param ssf          socket factory
     * @return an allocated local TCP port number
     * @see #setPortForwardingL(String bind_address, int lport, String host, int rport, ServerSocketFactory ssf, int connectTimeout)
     */
    public int setPortForwardingL(String bind_address, int lport, String host, int rport, ServerSocketFactory ssf) throws JSchException {
        return setPortForwardingL(bind_address, lport, host, rport, ssf, 0);
    }

    /**
     * Registers the local port forwarding.
     * If <code>bind_address</code> is an empty string
     * or <code>"*"</code>, the port should be available from all interfaces.
     * If <code>bind_address</code> is <code>"localhost"</code> or
     * <code>null</code>, the listening port will be bound for local use only.
     * If <code>lport</code> is <code>0</code>, the tcp port will be allocated.
     *
     * @param bind_address   bind address for local port forwarding
     * @param lport          local port for local port forwarding
     * @param host           host address for local port forwarding
     * @param rport          remote port number for local port forwarding
     * @param ssf            socket factory
     * @param connectTimeout timeout for establishing port connection
     * @return an allocated local TCP port number
     */
    public int setPortForwardingL(String bind_address, int lport, String host, int rport, ServerSocketFactory ssf, int connectTimeout) throws JSchException {
        PortWatcher pw = PortWatcher.addPort(this, bind_address, lport, host, rport, ssf);
        pw.setConnectTimeout(connectTimeout);
        Thread tmp = new Thread(pw);
        tmp.setName("PortWatcher Thread for " + host);
        if (daemon_thread) {
            tmp.setDaemon(daemon_thread);
        }
        tmp.start();
        return pw.lport;
    }

    /**
     * Cancels the local port forwarding assigned
     * at local TCP port <code>lport</code> on loopback interface.
     *
     * @param lport local TCP port
     */
    public void delPortForwardingL(int lport) throws JSchException {
        delPortForwardingL("127.0.0.1", lport);
    }

    /**
     * Cancels the local port forwarding assigned
     * at local TCP port <code>lport</code> on <code>bind_address</code> interface.
     *
     * @param bind_address bind_address of network interfaces
     * @param lport        local TCP port
     */
    public void delPortForwardingL(String bind_address, int lport) throws JSchException {
        PortWatcher.delPort(this, bind_address, lport);
    }

    /**
     * Lists the registered local port forwarding.
     *
     * @return a list of "lport:host:hostport"
     */
    public String[] getPortForwardingL() throws JSchException {
        return PortWatcher.getPortForwarding(this);
    }

    /**
     * Registers the remote port forwarding for the loopback interface
     * of the remote.
     *
     * @param rport remote port
     * @param host  host address
     * @param lport local port
     * @see #setPortForwardingR(String bind_address, int rport, String host, int lport, SocketFactory sf)
     */
    public void setPortForwardingR(int rport, String host, int lport) throws JSchException {
        setPortForwardingR(null, rport, host, lport, (SocketFactory) null);
    }

    /**
     * Registers the remote port forwarding.
     * If <code>bind_address</code> is an empty string or <code>"*"</code>,
     * the port should be available from all interfaces.
     * If <code>bind_address</code> is <code>"localhost"</code> or is not given,
     * the listening port will be bound for local use only.
     * Note that if <code>GatewayPorts</code> is <code>"no"</code> on the
     * remote, <code>"localhost"</code> is always used as a bind_address.
     *
     * @param bind_address bind address
     * @param rport        remote port
     * @param host         host address
     * @param lport        local port
     * @see #setPortForwardingR(String bind_address, int rport, String host, int lport, SocketFactory sf)
     */
    public void setPortForwardingR(String bind_address, int rport, String host, int lport) throws JSchException {
        setPortForwardingR(bind_address, rport, host, lport, (SocketFactory) null);
    }

    /**
     * Registers the remote port forwarding for the loopback interface
     * of the remote.
     *
     * @param rport remote port
     * @param host  host address
     * @param lport local port
     * @param sf    socket factory
     * @see #setPortForwardingR(String bind_address, int rport, String host, int lport, SocketFactory sf)
     */
    public void setPortForwardingR(int rport, String host, int lport, SocketFactory sf) throws JSchException {
        setPortForwardingR(null, rport, host, lport, sf);
    }

    // TODO: This method should return the integer value as the assigned port.

    /**
     * Registers the remote port forwarding.
     * If <code>bind_address</code> is an empty string or <code>"*"</code>,
     * the port should be available from all interfaces.
     * If <code>bind_address</code> is <code>"localhost"</code> or is not given,
     * the listening port will be bound for local use only.
     * Note that if <code>GatewayPorts</code> is <code>"no"</code> on the
     * remote, <code>"localhost"</code> is always used as a bind_address.
     * If <code>rport</code> is <code>0</code>, the TCP port will be allocated on the remote.
     *
     * @param bind_address bind address
     * @param rport        remote port
     * @param host         host address
     * @param lport        local port
     * @param sf           socket factory
     */
    public void setPortForwardingR(String bind_address, int rport, String host, int lport, SocketFactory sf) throws JSchException {
        int allocated = _setPortForwardingR(bind_address, rport);
        ChannelForwardedTCPIP.addPort(this, bind_address,
                rport, allocated, host, lport, sf);
    }

    /**
     * Registers the remote port forwarding for the loopback interface
     * of the remote.
     * The TCP connection to <code>rport</code> on the remote will be
     * forwarded to an instance of the class <code>daemon</code>.
     * The class specified by <code>daemon</code> must implement
     * <code>ForwardedTCPIPDaemon</code>.
     *
     * @param rport  remote port
     * @param daemon class name, which implements "ForwardedTCPIPDaemon"
     * @see #setPortForwardingR(String bind_address, int rport, String daemon, Object[] arg)
     */
    public void setPortForwardingR(int rport, String daemon) throws JSchException {
        setPortForwardingR(null, rport, daemon, null);
    }

    /**
     * Registers the remote port forwarding for the loopback interface
     * of the remote.
     * The TCP connection to <code>rport</code> on the remote will be
     * forwarded to an instance of the class <code>daemon</code> with
     * the argument <code>arg</code>.
     * The class specified by <code>daemon</code> must implement <code>ForwardedTCPIPDaemon</code>.
     *
     * @param rport  remote port
     * @param daemon class name, which implements "ForwardedTCPIPDaemon"
     * @param arg    arguments for "daemon"
     * @see #setPortForwardingR(String bind_address, int rport, String daemon, Object[] arg)
     */
    public void setPortForwardingR(int rport, String daemon, Object[] arg) throws JSchException {
        setPortForwardingR(null, rport, daemon, arg);
    }

    /**
     * Registers the remote port forwarding.
     * If <code>bind_address</code> is an empty string
     * or <code>"*"</code>, the port should be available from all interfaces.
     * If <code>bind_address</code> is <code>"localhost"</code> or is not given,
     * the listening port will be bound for local use only.
     * Note that if <code>GatewayPorts</code> is <code>"no"</code> on the
     * remote, <code>"localhost"</code> is always used as a bind_address.
     * The TCP connection to <code>rport</code> on the remote will be
     * forwarded to an instance of the class <code>daemon</code> with the
     * argument <code>arg</code>.
     * The class specified by <code>daemon</code> must implement <code>ForwardedTCPIPDaemon</code>.
     *
     * @param bind_address bind address
     * @param rport        remote port
     * @param daemon       class name, which implements "ForwardedTCPIPDaemon"
     * @param arg          arguments for "daemon"
     * @see #setPortForwardingR(String bind_address, int rport, String daemon, Object[] arg)
     */
    public void setPortForwardingR(String bind_address, int rport, String daemon, Object[] arg) throws JSchException {
        int allocated = _setPortForwardingR(bind_address, rport);
        ChannelForwardedTCPIP.addPort(this, bind_address,
                rport, allocated, daemon, arg);
    }

    /**
     * Lists the registered remote port forwarding.
     *
     * @return a list of "rport:host:hostport"
     */
    public String[] getPortForwardingR() throws JSchException {
        return ChannelForwardedTCPIP.getPortForwarding(this);
    }

    private class Forwarding {
        String bind_address = null;
        int port = -1;
        String host = null;
        int hostport = -1;
    }

    /**
     * The given argument may be "[bind_address:]port:host:hostport" or
     * "[bind_address:]port host:hostport", which is from LocalForward command of
     * ~/.ssh/config .
     */
    private Forwarding parseForwarding(String conf) throws JSchException {
        String[] tmp = conf.split(" ");
        if (tmp.length > 1) {   // "[bind_address:]port host:hostport"
            Vector foo = new Vector();
            for (int i = 0; i < tmp.length; i++) {
                if (tmp[i].length() == 0) continue;
                foo.addElement(tmp[i].trim());
            }
            StringBuffer sb = new StringBuffer(); // join
            for (int i = 0; i < foo.size(); i++) {
                sb.append((String) (foo.elementAt(i)));
                if (i + 1 < foo.size())
                    sb.append(":");
            }
            conf = sb.toString();
        }

        String org = conf;
        Forwarding f = new Forwarding();
        try {
            if (conf.lastIndexOf(":") == -1)
                throw new JSchException("parseForwarding: " + org);
            f.hostport = Integer.parseInt(conf.substring(conf.lastIndexOf(":") + 1));
            conf = conf.substring(0, conf.lastIndexOf(":"));
            if (conf.lastIndexOf(":") == -1)
                throw new JSchException("parseForwarding: " + org);
            f.host = conf.substring(conf.lastIndexOf(":") + 1);
            conf = conf.substring(0, conf.lastIndexOf(":"));
            if (conf.lastIndexOf(":") != -1) {
                f.port = Integer.parseInt(conf.substring(conf.lastIndexOf(":") + 1));
                conf = conf.substring(0, conf.lastIndexOf(":"));
                if (conf.length() == 0 || conf.equals("*")) conf = "0.0.0.0";
                if (conf.equals("localhost")) conf = "127.0.0.1";
                f.bind_address = conf;
            } else {
                f.port = Integer.parseInt(conf);
                f.bind_address = "127.0.0.1";
            }
        } catch (NumberFormatException e) {
            throw new JSchException("parseForwarding: " + e.toString());
        }
        return f;
    }

    /**
     * Registers the local port forwarding.  The argument should be
     * in the format like "[bind_address:]port:host:hostport".
     * If <code>bind_address</code> is an empty string or <code>"*"</code>,
     * the port should be available from all interfaces.
     * If <code>bind_address</code> is <code>"localhost"</code> or is not given,
     * the listening port will be bound for local use only.
     *
     * @param conf configuration of local port forwarding
     * @return an assigned port number
     * @see #setPortForwardingL(String bind_address, int lport, String host, int rport)
     */
    public int setPortForwardingL(String conf) throws JSchException {
        Forwarding f = parseForwarding(conf);
        return setPortForwardingL(f.bind_address, f.port, f.host, f.hostport);
    }

    /**
     * Registers the remote port forwarding.  The argument should be
     * in the format like "[bind_address:]port:host:hostport".  If the
     * bind_address is not given, the default is to only bind to loopback
     * addresses.  If the bind_address is <code>"*"</code> or an empty string,
     * then the forwarding is requested to listen on all interfaces.
     * Note that if <code>GatewayPorts</code> is <code>"no"</code> on the remote,
     * <code>"localhost"</code> is always used for bind_address.
     * If the specified remote is <code>"0"</code>,
     * the TCP port will be allocated on the remote.
     *
     * @param conf configuration of remote port forwarding
     * @return an allocated TCP port on the remote.
     * @see #setPortForwardingR(String bind_address, int rport, String host, int rport)
     */
    public int setPortForwardingR(String conf) throws JSchException {
        Forwarding f = parseForwarding(conf);
        int allocated = _setPortForwardingR(f.bind_address, f.port);
        ChannelForwardedTCPIP.addPort(this, f.bind_address,
                f.port, allocated, f.host, f.hostport, null);
        return allocated;
    }

    /**
     * Instantiates an instance of stream-forwarder to <code>host</code>:<code>port</code>.
     * Set I/O stream to the given channel, and then invoke Channel#connect() method.
     *
     * @param host remote host, which the given stream will be plugged to.
     * @param port remote port, which the given stream will be plugged to.
     */
    public Channel getStreamForwarder(String host, int port) throws JSchException {
        ChannelDirectTCPIP channel = new ChannelDirectTCPIP();
        channel.init();
        this.addChannel(channel);
        channel.setHost(host);
        channel.setPort(port);
        return channel;
    }

    private class GlobalRequestReply {
        private Thread thread = null;
        private int reply = -1;
        private int port = 0;

        void setThread(Thread thread) {
            this.thread = thread;
            this.reply = -1;
        }

        Thread getThread() {
            return thread;
        }

        void setReply(int reply) {
            this.reply = reply;
        }

        int getReply() {
            return this.reply;
        }

        int getPort() {
            return this.port;
        }

        void setPort(int port) {
            this.port = port;
        }
    }

    private GlobalRequestReply grr = new GlobalRequestReply();

    private int _setPortForwardingR(String bind_address, int rport) throws JSchException {
        synchronized (grr) {
            Buffer buf = new Buffer(100); // ??
            Packet packet = new Packet(buf);

            String address_to_bind = ChannelForwardedTCPIP.normalize(bind_address);

            grr.setThread(Thread.currentThread());
            grr.setPort(rport);

            try {
                packet.reset();
                buf.putByte((byte) SSH_MSG_GLOBAL_REQUEST);
                buf.putString(Util.str2byte("tcpip-forward"));
                buf.putByte((byte) 1);
                buf.putString(Util.str2byte(address_to_bind));
                buf.putInt(rport);
                write(packet);
            } catch (Exception e) {
                grr.setThread(null);
                if (e instanceof Throwable)
                    throw new JSchException(e.toString(), (Throwable) e);
                throw new JSchException(e.toString());
            }

            int count = 0;
            int reply = grr.getReply();
            while (count < 10 && reply == -1) {
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                }
                count++;
                reply = grr.getReply();
            }
            grr.setThread(null);
            if (reply != 1) {
                throw new JSchException("remote port forwarding failed for listen port " + rport);
            }
            rport = grr.getPort();
        }
        return rport;
    }

    /**
     * Cancels the remote port forwarding assigned at remote TCP port <code>rport</code>.
     *
     * @param rport remote TCP port
     */
    public void delPortForwardingR(int rport) throws JSchException {
        this.delPortForwardingR(null, rport);
    }

    /**
     * Cancels the remote port forwarding assigned at
     * remote TCP port <code>rport</code> bound on the interface at
     * <code>bind_address</code>.
     *
     * @param bind_address bind address of the interface on the remote
     * @param rport        remote TCP port
     */
    public void delPortForwardingR(String bind_address, int rport) throws JSchException {
        ChannelForwardedTCPIP.delPort(this, bind_address, rport);
    }

    /**
     * 2025.7.15: 初始化压缩器（压缩发往服务器端的报文）
     *
     * @param method
     * @throws JSchException
     */
    private void initDeflater(String method) throws JSchException {
        if (method.equals("none")) {
            deflater = null;
            return;
        }
        String foo = getConfig(method);
        if (foo != null) {
            if (method.equals("zlib") ||
                    (isAuthed && method.equals("zlib@openssh.com"))) {
                try {
                    Class c = Class.forName(foo);
                    deflater = (Compression) (c.newInstance());
                    int level = 6;
                    try {
                        level = Integer.parseInt(getConfig("compression_level"));
                    } catch (Exception ee) {
                    }
                    deflater.init(Compression.DEFLATER, level);
                } catch (NoClassDefFoundError ee) {
                    throw new JSchException(ee.toString(), ee);
                } catch (Exception ee) {
                    throw new JSchException(ee.toString(), ee);
                }
            }
        }
    }

    /**
     * 2025.7.15: 初始化压缩和解压工具类
     *
     * @param method
     * @throws JSchException
     */
    private void initInflater(String method) throws JSchException {
        if (method.equals("none")) {
            inflater = null;
            return;
        }
        String foo = getConfig(method);
        if (foo != null) {
            if (method.equals("zlib") ||
                    (isAuthed && method.equals("zlib@openssh.com"))) {
                try {
                    Class c = Class.forName(foo);
                    inflater = (Compression) (c.newInstance());
                    inflater.init(Compression.INFLATER, 0);
                } catch (Exception ee) {
                    throw new JSchException(ee.toString(), ee);
                }
            }
        }
    }

    void addChannel(Channel channel) {
        channel.setSession(this);
    }

    public void setProxy(Proxy proxy) {
        this.proxy = proxy;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public void setPort(int port) {
        this.port = port;
    }

    void setUserName(String username) {
        this.username = username;
    }

    public void setUserInfo(UserInfo userinfo) {
        this.userinfo = userinfo;
    }

    public UserInfo getUserInfo() {
        return userinfo;
    }

    public void setInputStream(InputStream in) {
        this.in = in;
    }

    public void setOutputStream(OutputStream out) {
        this.out = out;
    }

    public void setX11Host(String host) {
        ChannelX11.setHost(host);
    }

    public void setX11Port(int port) {
        ChannelX11.setPort(port);
    }

    public void setX11Cookie(String cookie) {
        ChannelX11.setCookie(cookie);
    }

    public void setPassword(String password) {
        if (password != null)
            this.password = Util.str2byte(password);
    }

    public void setPassword(byte[] password) {
        if (password != null) {
            this.password = new byte[password.length];
            System.arraycopy(password, 0, this.password, 0, password.length);
        }
    }

    public void setConfig(java.util.Properties newconf) {
        setConfig((java.util.Hashtable) newconf);
    }

    public void setConfig(java.util.Hashtable newconf) {
        synchronized (lock) {
            if (config == null)
                config = new java.util.Hashtable();
            for (java.util.Enumeration e = newconf.keys(); e.hasMoreElements(); ) {
                String key = (String) (e.nextElement());
                config.put(key, (String) (newconf.get(key)));
            }
        }
    }

    public void setConfig(String key, String value) {
        synchronized (lock) {
            if (config == null) {
                config = new java.util.Hashtable();
            }
            config.put(key, value);
        }
    }

    public String getConfig(String key) {
        Object foo = null;
        if (config != null) {
            foo = config.get(key);
            if (foo instanceof String) return (String) foo;
        }
        foo = jsch.getConfig(key);
        if (foo instanceof String) return (String) foo;
        return null;
    }

    public void setSocketFactory(SocketFactory sfactory) {
        socket_factory = sfactory;
    }

    public boolean isConnected() {
        return isConnected;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) throws JSchException {
        if (socket == null) {
            if (timeout < 0) {
                throw new JSchException("invalid timeout value");
            }
            this.timeout = timeout;
            return;
        }
        try {
            socket.setSoTimeout(timeout);
            this.timeout = timeout;
        } catch (Exception e) {
            if (e instanceof Throwable)
                throw new JSchException(e.toString(), (Throwable) e);
            throw new JSchException(e.toString());
        }
    }

    public String getServerVersion() {
        return Util.byte2str(serverVersion);
    }

    public String getClientVersion() {
        return Util.byte2str(clientVersion);
    }

    public void setClientVersion(String cv) {
        clientVersion = Util.str2byte(cv);
    }

    public void sendIgnore() throws Exception {
        Buffer buf = new Buffer();
        Packet packet = new Packet(buf);
        packet.reset();
        buf.putByte((byte) SSH_MSG_IGNORE);
        write(packet);
    }

    private static final byte[] keepalivemsg = Util.str2byte("keepalive@jcraft.com");

    public void sendKeepAliveMsg() throws Exception {
        Buffer buf = new Buffer();
        Packet packet = new Packet(buf);
        packet.reset();
        buf.putByte((byte) SSH_MSG_GLOBAL_REQUEST);
        buf.putString(keepalivemsg);
        buf.putByte((byte) 1);
        write(packet);
    }

    private static final byte[] nomoresessions = Util.str2byte("no-more-sessions@openssh.com");

    public void noMoreSessionChannels() throws Exception {
        Buffer buf = new Buffer();
        Packet packet = new Packet(buf);
        packet.reset();
        buf.putByte((byte) SSH_MSG_GLOBAL_REQUEST);
        buf.putString(nomoresessions);
        buf.putByte((byte) 0);
        write(packet);
    }

    private HostKey hostkey = null;

    public HostKey getHostKey() {
        return hostkey;
    }

    public String getHost() {
        return host;
    }

    public String getUserName() {
        return username;
    }

    public int getPort() {
        return port;
    }

    public void setHostKeyAlias(String hostKeyAlias) {
        this.hostKeyAlias = hostKeyAlias;
    }

    public String getHostKeyAlias() {
        return hostKeyAlias;
    }

    /**
     * Sets the interval to send a keep-alive message.  If zero is
     * specified, any keep-alive message must not be sent.  The default interval
     * is zero.
     *
     * @param interval the specified interval, in milliseconds.
     * @see #getServerAliveInterval()
     */
    public void setServerAliveInterval(int interval) throws JSchException {
        setTimeout(interval);
        this.serverAliveInterval = interval;
    }

    /**
     * Returns setting for the interval to send a keep-alive message.
     *
     * @see #setServerAliveInterval(int)
     */
    public int getServerAliveInterval() {
        return this.serverAliveInterval;
    }

    /**
     * Sets the number of keep-alive messages which may be sent without
     * receiving any messages back from the server.  If this threshold is
     * reached while keep-alive messages are being sent, the connection will
     * be disconnected.  The default value is one.
     *
     * @param count the specified count
     * @see #getServerAliveCountMax()
     */
    public void setServerAliveCountMax(int count) {
        this.serverAliveCountMax = count;
    }

    /**
     * Returns setting for the threshold to send keep-alive messages.
     *
     * @see #setServerAliveCountMax(int)
     */
    public int getServerAliveCountMax() {
        return this.serverAliveCountMax;
    }

    public void setDaemonThread(boolean enable) {
        this.daemon_thread = enable;
    }

    /**
     * 2025.7.16: 检查加密算法的实现类是否存在
     *
     * @param ciphers
     * @return
     */
    private String[] checkCiphers(String ciphers) {
        if (ciphers == null || ciphers.length() == 0)
            return null;

        log.info("CheckCiphers: " + ciphers);

        String cipherc2s = getConfig("cipher.c2s");
        String ciphers2c = getConfig("cipher.s2c");

        Vector result = new Vector();
        String[] _ciphers = Util.split(ciphers, ",");
        for (int i = 0; i < _ciphers.length; i++) {
            String cipher = _ciphers[i];
            if (ciphers2c.indexOf(cipher) == -1 && cipherc2s.indexOf(cipher) == -1)
                continue;
            if (!checkCipherAndInit(getConfig(cipher))) {
                result.addElement(cipher);
            }
        }
        if (result.size() == 0) {
            return null;
        }
        String[] foo = new String[result.size()];
        System.arraycopy(result.toArray(), 0, foo, 0, result.size());

        if (JSch.getLogger().isEnabled(Logger.INFO)) {
            for (int i = 0; i < foo.length; i++) {
                log.info(foo[i] + " is not available.");
            }
        }

        return foo;
    }

    /**
     * 2025.7.12: 检查加密算法的实现类是否存在；
     * 如果存在，则实例化并初始化
     *
     * @param cipher
     * @return
     */
    static boolean checkCipherAndInit(String cipher) {
        try {
            Class c = Class.forName(cipher);
            Cipher _c = (Cipher) (c.newInstance());
            _c.init(Cipher.ENCRYPT_MODE,
                    new byte[_c.getBlockSize()],
                    new byte[_c.getIVSize()]);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 2025.7.15：检查密钥交换实现类是否存在；如果存在，则初始化
     *
     * @param kexes
     * @return
     */
    private String[] checkKexes(String kexes) {
        if (kexes == null || kexes.length() == 0)
            return null;

        log.info("CheckKexes: " + kexes);


        java.util.Vector result = new java.util.Vector();
        String[] _kexes = Util.split(kexes, ",");
        for (int i = 0; i < _kexes.length; i++) {
            if (!checkKexAndInit(this, getConfig(_kexes[i]))) {
                result.addElement(_kexes[i]);
            }
        }
        if (result.size() == 0)
            return null;
        String[] foo = new String[result.size()];
        System.arraycopy(result.toArray(), 0, foo, 0, result.size());

        if (JSch.getLogger().isEnabled(Logger.INFO)) {
            for (int i = 0; i < foo.length; i++) {
                JSch.getLogger().log(Logger.INFO,
                        foo[i] + " is not available.");
            }
        }

        return foo;
    }

    /**
     * 2025.7.15: 检查密钥交换算法，并实例化+初始化
     *
     * @param s
     * @param kex
     * @return
     */
    static boolean checkKexAndInit(Session s, String kex) {
        try {
            Class c = Class.forName(kex);
            KeyExchange _c = (KeyExchange) (c.newInstance());
            _c.init(s, null, null, null, null);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 2025.7.15: 检查签名算法的实现类是否存在；
     * 如果存在，则实例化，并初始化
     *
     * @param sigs
     * @return
     */
    private String[] checkSignatures(String sigs) {
        if (sigs == null || sigs.length() == 0)
            return null;

        log.info("CheckSignatures: " + sigs);

        java.util.Vector result = new java.util.Vector();
        String[] _sigs = Util.split(sigs, ",");
        for (int i = 0; i < _sigs.length; i++) {
            try {
                Class c = Class.forName((String) jsch.getConfig(_sigs[i]));
                final Signature sig = (Signature) (c.newInstance());
                sig.init();
            } catch (Exception e) {
                result.addElement(_sigs[i]);
            }
        }
        if (result.size() == 0)
            return null;
        String[] foo = new String[result.size()];
        System.arraycopy(result.toArray(), 0, foo, 0, result.size());
        if (JSch.getLogger().isEnabled(Logger.INFO)) {
            for (int i = 0; i < foo.length; i++) {
                JSch.getLogger().log(Logger.INFO,
                        foo[i] + " is not available.");
            }
        }
        return foo;
    }

    /**
     * Sets the identityRepository, which will be referred
     * in the public key authentication.  The default value is <code>null</code>.
     *
     * @param identityRepository
     * @see #getIdentityRepository()
     */
    public void setIdentityRepository(IdentityRepository identityRepository) {
        this.identityRepository = identityRepository;
    }

    /**
     * Gets the identityRepository.
     * If this.identityRepository is <code>null</code>,
     * JSch#getIdentityRepository() will be invoked.
     *
     * @see JSch#getIdentityRepository()
     */
    IdentityRepository getIdentityRepository() {
        if (identityRepository == null)
            return jsch.getIdentityRepository();
        return identityRepository;
    }

    /**
     * Sets the hostkeyRepository, which will be referred in checking host keys.
     *
     * @param hostkeyRepository
     * @see #getHostKeyRepository()
     */
    public void setHostKeyRepository(HostKeyRepository hostkeyRepository) {
        this.hostkeyRepository = hostkeyRepository;
    }

    /**
     * Gets the hostkeyRepository.
     * If this.hostkeyRepository is <code>null</code>,
     * JSch#getHostKeyRepository() will be invoked.
     *
     * @see JSch#getHostKeyRepository()
     */
    public HostKeyRepository getHostKeyRepository() {
        if (hostkeyRepository == null)
            return jsch.getHostKeyRepository();
        return hostkeyRepository;
    }

    /**
     * 2025.6.27: 应用配置
     *
     * @throws JSchException
     */
    private void applyConfig() throws JSchException {
        ConfigRepository configRepository = jsch.getConfigRepository();
        if (configRepository == null) {
            return;
        }

        ConfigRepository.Config config =
                configRepository.getConfig(org_host);

        String value = null;

        value = config.getUser();
        if (value != null)
            username = value;

        value = config.getHostname();
        if (value != null)
            host = value;

        int port = config.getPort();
        if (port != -1)
            this.port = port;

        checkConfig(config, "kex");
        checkConfig(config, "server_host_key");

        checkConfig(config, "cipher.c2s");
        checkConfig(config, "cipher.s2c");
        checkConfig(config, "mac.c2s");
        checkConfig(config, "mac.s2c");
        checkConfig(config, "compression.c2s");
        checkConfig(config, "compression.s2c");
        checkConfig(config, "compression_level");

        checkConfig(config, "StrictHostKeyChecking");
        checkConfig(config, "HashKnownHosts");
        checkConfig(config, "PreferredAuthentications");
        checkConfig(config, "MaxAuthTries");
        checkConfig(config, "ClearAllForwardings");

        value = config.getValue("HostKeyAlias");
        if (value != null)
            this.setHostKeyAlias(value);

        value = config.getValue("UserKnownHostsFile");
        if (value != null) {
            KnownHosts kh = new KnownHosts(jsch);
            kh.setKnownHosts(value);
            this.setHostKeyRepository(kh);
        }

        String[] values = config.getValues("IdentityFile");
        if (values != null) {
            String[] global =
                    configRepository.getConfig("").getValues("IdentityFile");
            if (global != null) {
                for (int i = 0; i < global.length; i++) {
                    jsch.addIdentity(global[i]);
                }
            } else {
                global = new String[0];
            }
            if (values.length - global.length > 0) {
                IdentityRepository.Wrapper ir =
                        new IdentityRepository.Wrapper(jsch.getIdentityRepository(), true);
                for (int i = 0; i < values.length; i++) {
                    String ifile = values[i];
                    for (int j = 0; j < global.length; j++) {
                        if (!ifile.equals(global[j]))
                            continue;
                        ifile = null;
                        break;
                    }
                    if (ifile == null)
                        continue;
                    Identity identity =
                            IdentityFile.newInstance(ifile, null, jsch);
                    ir.add(identity);
                }
                this.setIdentityRepository(ir);
            }
        }

        value = config.getValue("ServerAliveInterval");
        if (value != null) {
            try {
                this.setServerAliveInterval(Integer.parseInt(value));
            } catch (NumberFormatException e) {
            }
        }

        value = config.getValue("ConnectTimeout");
        if (value != null) {
            try {
                setTimeout(Integer.parseInt(value));
            } catch (NumberFormatException e) {
            }
        }

        value = config.getValue("MaxAuthTries");
        if (value != null) {
            setConfig("MaxAuthTries", value);
        }

        value = config.getValue("ClearAllForwardings");
        if (value != null) {
            setConfig("ClearAllForwardings", value);
        }

    }

    private void applyConfigChannel(ChannelSession channel) throws JSchException {
        ConfigRepository configRepository = jsch.getConfigRepository();
        if (configRepository == null) {
            return;
        }

        ConfigRepository.Config config =
                configRepository.getConfig(org_host);

        String value = null;

        value = config.getValue("ForwardAgent");
        if (value != null) {
            channel.setAgentForwarding(value.equals("yes"));
        }

        value = config.getValue("RequestTTY");
        if (value != null) {
            channel.setPty(value.equals("yes"));
        }
    }

    private void requestPortForwarding() throws JSchException {

        if (getConfig("ClearAllForwardings").equals("yes"))
            return;

        ConfigRepository configRepository = jsch.getConfigRepository();
        if (configRepository == null) {
            return;
        }

        ConfigRepository.Config config =
                configRepository.getConfig(org_host);

        String[] values = config.getValues("LocalForward");
        if (values != null) {
            for (int i = 0; i < values.length; i++) {
                setPortForwardingL(values[i]);
            }
        }

        values = config.getValues("RemoteForward");
        if (values != null) {
            for (int i = 0; i < values.length; i++) {
                setPortForwardingR(values[i]);
            }
        }
    }

    private void checkConfig(ConfigRepository.Config config, String key) {
        String value = config.getValue(key);
        if (value != null)
            this.setConfig(key, value);
    }
}
